From ee5c35d502cfdab656e58601b7b062b550ec4d6c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 27 Feb 2018 17:59:46 +1100 Subject: Move downloads into their own window that can be toggled on/off --- install/onionshare.nsi | 4 ++++ onionshare_gui/downloads.py | 16 ++++++++++++---- onionshare_gui/onionshare_gui.py | 30 +++++++++++++++++++----------- share/images/download_window_gray.png | 0 share/images/download_window_green.png | 0 share/locale/en.json | 4 +++- 6 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 share/images/download_window_gray.png create mode 100644 share/images/download_window_green.png diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 4030636a..f47541cf 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -207,6 +207,8 @@ Section "install" File "${BINPATH}\share\images\download_completed_none.png" File "${BINPATH}\share\images\download_in_progress.png" File "${BINPATH}\share\images\download_in_progress_none.png" + File "${BINPATH}\share\images\download_window_gray.png" + File "${BINPATH}\share\images\download_window_green.png" File "${BINPATH}\share\images\favicon.ico" File "${BINPATH}\share\images\file_delete.png" File "${BINPATH}\share\images\info.png" @@ -393,6 +395,8 @@ FunctionEnd Delete "$INSTDIR\share\images\download_completed_none.png" Delete "$INSTDIR\share\images\download_in_progress.png" Delete "$INSTDIR\share\images\download_in_progress_none.png" + Delete "$INSTDIR\share\images\download_window_gray.png" + Delete "$INSTDIR\share\images\download_window_green.png" Delete "$INSTDIR\share\images\favicon.ico" Delete "$INSTDIR\share\images\file_delete.png" Delete "$INSTDIR\share\images\info.png" diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 166f14a4..200d0649 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -19,7 +19,7 @@ along with this program. If not, see . """ import time -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common @@ -98,19 +98,27 @@ class Downloads(QtWidgets.QWidget): def __init__(self): super(Downloads, self).__init__() self.downloads = {} + self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) self.layout = QtWidgets.QVBoxLayout() + self.layout.addWidget(self.downloads_label) + self.layout.addWidget(self.no_downloads_label) self.setLayout(self.layout) + self.setWindowTitle(strings._('gui_downloads', True)) + 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) def add_download(self, download_id, total_bytes): """ Add a new download progress bar. """ - self.parent().show() - # add it to the list + self.no_downloads_label.hide() download = Download(download_id, total_bytes) self.downloads[download_id] = download - self.layout.insertWidget(-1, download.progress_bar) + self.layout.addWidget(download.progress_bar) def update_download(self, download_id, downloaded_bytes): """ diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f38dd727..a94ae95f 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -103,13 +103,6 @@ class OnionShareGui(QtWidgets.QMainWindow): # Downloads self.downloads = Downloads() - self.downloads_container = QtWidgets.QScrollArea() - self.downloads_container.setWidget(self.downloads) - self.downloads_container.setWidgetResizable(True) - self.downloads_container.setMaximumHeight(200) - self.downloads_container.setMinimumHeight(75) - self.vbar = self.downloads_container.verticalScrollBar() - self.downloads_container.hide() # downloads start out hidden self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -119,6 +112,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_show_downloads = QtWidgets.QToolButton() + self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setCheckable(True) + self.info_show_downloads.toggled.connect(self.downloads_toggled) + self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') @@ -132,6 +131,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) + self.info_layout.addWidget(self.info_show_downloads) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -189,7 +189,6 @@ class OnionShareGui(QtWidgets.QMainWindow): primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.filesize_warning) - primary_action_layout.addWidget(self.downloads_container) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(primary_action_layout) self.primary_action.hide() @@ -370,7 +369,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app.set_stealth(self.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared - self.downloads_container.hide() self.downloads.reset_downloads() self.reset_info_counters() self.status_bar.clearMessage() @@ -562,7 +560,6 @@ class OnionShareGui(QtWidgets.QMainWindow): # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: - self.vbar.setValue(self.vbar.maximum()) self.new_download = False events = [] @@ -580,7 +577,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.showMessage(strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: - self.downloads_container.show() # show the downloads layout self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 @@ -647,6 +643,16 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + def downloads_toggled(self, checked): + """ + When the 'Show/hide downloads' button is toggled, show or hide the downloads window. + """ + common.log('OnionShareGui', 'toggle_downloads') + if checked: + self.downloads.show() + else: + self.downloads.hide() + def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. @@ -687,6 +693,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) + self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) def update_downloads_completed(self, count): """ @@ -707,6 +714,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') else: self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') + self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) diff --git a/share/images/download_window_gray.png b/share/images/download_window_gray.png new file mode 100644 index 00000000..e69de29b diff --git a/share/images/download_window_green.png b/share/images/download_window_green.png new file mode 100644 index 00000000..e69de29b diff --git a/share/locale/en.json b/share/locale/en.json index 09ead591..e3bfe08e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -44,7 +44,9 @@ "gui_stop_server_shutdown_timeout_tooltip": "Share will stop automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Download History", + "gui_downloads_window_tooltip": "Show/hide downloads", + "gui_no_downloads": "No downloads yet.", "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare address", "gui_copied_url": "The OnionShare address has been copied to clipboard", -- cgit v1.2.3-54-g00ecf From 8961bd1ed0edd66fee22f5b2467b62915297e53f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 27 Feb 2018 18:08:52 +1100 Subject: Add missing images for the downloads toggle button --- share/images/download_window_gray.png | Bin 0 -> 440 bytes share/images/download_window_green.png | Bin 0 -> 761 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/share/images/download_window_gray.png b/share/images/download_window_gray.png index e69de29b..bf9c168e 100644 Binary files a/share/images/download_window_gray.png and b/share/images/download_window_gray.png differ diff --git a/share/images/download_window_green.png b/share/images/download_window_green.png index e69de29b..8f9a899b 100644 Binary files a/share/images/download_window_green.png and b/share/images/download_window_green.png differ -- cgit v1.2.3-54-g00ecf From fffc420cb1bdc45bab5e5360108ac7c80eac3f56 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 27 Feb 2018 18:20:05 +1100 Subject: re-show the 'No downloads yet' label when resetting the counters (e.g starting a new share) --- onionshare_gui/onionshare_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index a94ae95f..818a7d95 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -694,6 +694,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_downloads_completed(0) self.update_downloads_in_progress(0) self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.downloads.no_downloads_label.show() def update_downloads_completed(self, count): """ -- cgit v1.2.3-54-g00ecf From fa9e5748a392c3504acb533ae73d718b0ebf68f2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 28 Feb 2018 09:48:23 +1100 Subject: Make downloads window scrollable again. Reset to original size when starting a fresh share --- onionshare_gui/downloads.py | 18 +++++++++++++----- onionshare_gui/onionshare_gui.py | 7 +++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 200d0649..82b2366a 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -98,24 +98,32 @@ class Downloads(QtWidgets.QWidget): def __init__(self): super(Downloads, self).__init__() self.downloads = {} + + self.downloads_container = QtWidgets.QScrollArea() + self.downloads_container.setWidget(self) + self.downloads_container.setWindowTitle(strings._('gui_downloads', True)) + self.downloads_container.setWidgetResizable(True) + self.downloads_container.setMaximumHeight(600) + self.downloads_container.setMinimumHeight(150) + self.downloads_container.setMinimumWidth(350) + self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) + self.downloads_container.vbar = self.downloads_container.verticalScrollBar() + self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.downloads_label) self.layout.addWidget(self.no_downloads_label) self.setLayout(self.layout) - self.setWindowTitle(strings._('gui_downloads', True)) - 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) def add_download(self, download_id, total_bytes): """ Add a new download progress bar. """ # add it to the list - self.no_downloads_label.hide() download = Download(download_id, total_bytes) self.downloads[download_id] = download self.layout.addWidget(download.progress_bar) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 818a7d95..c3229000 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -560,6 +560,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: + self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) self.new_download = False events = [] @@ -577,6 +578,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.showMessage(strings._('download_page_loaded', True)) elif event["type"] == web.REQUEST_DOWNLOAD: + self.downloads.no_downloads_label.hide() self.downloads.add_download(event["data"]["id"], web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 @@ -649,9 +651,9 @@ class OnionShareGui(QtWidgets.QMainWindow): """ common.log('OnionShareGui', 'toggle_downloads') if checked: - self.downloads.show() + self.downloads.downloads_container.show() else: - self.downloads.hide() + self.downloads.downloads_container.hide() def copy_url(self): """ @@ -695,6 +697,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_downloads_in_progress(0) self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) self.downloads.no_downloads_label.show() + self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) def update_downloads_completed(self, count): """ -- cgit v1.2.3-54-g00ecf From c5384316d98cd8375c9b2793aba6a22c0e3ee460 Mon Sep 17 00:00:00 2001 From: Baccount Date: Fri, 2 Mar 2018 18:43:30 -0800 Subject: Update en.json --- share/locale/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index 09ead591..fe5f030a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -51,7 +51,7 @@ "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", "gui_starting_server1": "Starting Tor onion service...", - "gui_starting_server2": "Crunching files...", + "gui_starting_server2": "Compressing files...", "gui_please_wait": "Starting... Click to cancel", "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", @@ -65,7 +65,7 @@ "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", - "zip_progress_bar_format": "Crunching files: %p%", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", -- cgit v1.2.3-54-g00ecf From 947bfe3d89852c0c524f0dea8c7e29176487f305 Mon Sep 17 00:00:00 2001 From: Baccount Date: Fri, 2 Mar 2018 18:44:52 -0800 Subject: Update eo.json --- share/locale/eo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/eo.json b/share/locale/eo.json index 6d904dc0..8060f815 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -34,7 +34,7 @@ "gui_copied_url": "URL kopiita en tondujon", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", "gui_starting_server1": "Startigas Tor onion service...", - "gui_starting_server2": "Crunching files...", + "gui_starting_server2": "Compressing files...", "gui_please_wait": "Bonvolu atendi...", "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", @@ -47,7 +47,7 @@ "gui_quit_warning_quit": "Foriri", "gui_quit_warning_dont_quit": "Ne foriri", "error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.", - "zip_progress_bar_format": "Crunching files: %p%", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", "gui_menu_file_menu": "&File", -- cgit v1.2.3-54-g00ecf From 9da1b82e5a0cadf64843688e6c651f239bb64b6d Mon Sep 17 00:00:00 2001 From: Baccount Date: Fri, 2 Mar 2018 18:45:36 -0800 Subject: Update onionshare_gui.py --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f38dd727..7dd641dd 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -412,7 +412,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ common.log('OnionShareGui', 'start_server_step2') - # add progress bar to the status bar, indicating the crunching of files. + # add progress bar to the status bar, indicating the compressing of files. self._zip_progress_bar = ZipProgressBar(0) self.filenames = [] for index in range(self.file_selection.file_list.count()): -- cgit v1.2.3-54-g00ecf From a71786d02495f2a1bf6d26d470acee2f97452211 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 5 Mar 2018 13:52:50 +1100 Subject: Warn about the costs of using meek_lite bridges --- onionshare_gui/settings_dialog.py | 6 ++++++ share/locale/en.json | 1 + 2 files changed, 7 insertions(+) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7c81afc6..adf0657c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -496,6 +496,9 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_custom_textbox_options.hide() + # 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_amazon'): + Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ @@ -503,6 +506,9 @@ class SettingsDialog(QtWidgets.QDialog): """ if checked: self.tor_bridges_use_custom_textbox_options.hide() + # 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'): + Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_custom_radio_toggled(self, checked): """ diff --git a/share/locale/en.json b/share/locale/en.json index 09ead591..5f5adf05 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -104,6 +104,7 @@ "gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!

You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid.\nPlease try again with valid bridges.", -- cgit v1.2.3-54-g00ecf From 18ac830a9e63d44bb5c1bbbbe85e01235b6b14e3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 5 Mar 2018 07:45:10 -0800 Subject: Add command line flag for receive mode --- onionshare/__init__.py | 13 ++++++++++--- onionshare/onionshare.py | 5 ++++- onionshare_gui/__init__.py | 2 +- share/locale/en.json | 2 ++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 76d2b601..ad1d300b 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -44,9 +44,10 @@ def main(cwd=None): parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--stealth', action='store_true', dest='stealth', help=strings._("help_stealth")) - parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) + parser.add_argument('--receive', action='store_true', dest='receive', help=strings._("help_receive")) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) - parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename')) + parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) + parser.add_argument('filename', metavar='filename', nargs='*', help=strings._('help_filename')) args = parser.parse_args() filenames = args.filename @@ -58,8 +59,14 @@ def main(cwd=None): stay_open = bool(args.stay_open) shutdown_timeout = int(args.shutdown_timeout) stealth = bool(args.stealth) + receive = bool(args.receive) config = args.config + # Make sure filenames given if not using receiver mode + if not receive and len(filenames) == 0: + print(strings._('no_filenames')) + sys.exit() + # Debug mode? if debug: common.set_debug(debug) @@ -92,7 +99,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(onion, receive, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) app.start_onion_service() except KeyboardInterrupt: diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 85bfaf22..a8f2ceb0 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -27,7 +27,7 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0): + def __init__(self, onion, receive, local_only=False, stay_open=False, shutdown_timeout=0): common.log('OnionShare', '__init__') # The Onion object @@ -37,6 +37,9 @@ class OnionShare(object): self.onion_host = None self.stealth = None + # Receiver mode + self.receive = receive + # files and dirs to delete on shutdown self.cleanup_filenames = [] diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 24e627bb..945487fc 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -105,7 +105,7 @@ def main(): # Start the OnionShare app web.set_stay_open(stay_open) - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(onion, False, local_only, stay_open, shutdown_timeout) # Launch the gui gui = OnionShareGui(onion, qtapp, app, filenames, config) diff --git a/share/locale/en.json b/share/locale/en.json index 09ead591..e72686b8 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -10,6 +10,7 @@ "ctrlc_to_stop": "Press Ctrl-C to stop server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", + "no_filenames": "You must specify a list of files to share.", "no_available_port": "Could not start the Onion service as there was no available port.", "download_page_loaded": "Download page loaded", "other_page_loaded": "Address loaded", @@ -31,6 +32,7 @@ "help_shutdown_timeout": "Shut down the onion service after N seconds", "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", + "help_receive": "Receive files instead of sending them", "help_debug": "Log application errors to stdout, and log web errors to disk", "help_filename": "List of files or folders to share", "help_config": "Path to a custom JSON config file (optional)", -- cgit v1.2.3-54-g00ecf From 383ccb94fc047d81bddcefccd4511f11a7ebd61a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 5 Mar 2018 07:52:51 -0800 Subject: Add downloads_dir to settings, make it default to ~/Downloads --- onionshare/__init__.py | 2 +- onionshare/settings.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index ad1d300b..fe7f72dd 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -86,6 +86,7 @@ def main(cwd=None): settings = Settings(config) + settings.load() # Start the Onion object onion = Onion() @@ -122,7 +123,6 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - settings.load() t = threading.Thread(target=web.start, args=(app.port, app.stay_open, settings.get('slug'))) t.daemon = True t.start() diff --git a/onionshare/settings.py b/onionshare/settings.py index 545915e8..5ec2d2ae 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -70,7 +70,8 @@ class Settings(object): 'save_private_key': False, 'private_key': '', 'slug': '', - 'hidservauth_string': '' + 'hidservauth_string': '', + 'downloads_dir': self.build_default_downloads_dir() } self._settings = {} self.fill_in_defaults() @@ -97,6 +98,14 @@ class Settings(object): else: return os.path.expanduser('~/.config/onionshare/onionshare.json') + def build_default_downloads_dir(self): + """ + Returns the path of the default Downloads directory for receive mode. + """ + # TODO: Test in Windows, though it looks like it should work + # https://docs.python.org/3/library/os.path.html#os.path.expanduser + return os.path.expanduser('~/Downloads') + def load(self): """ Load the settings from file. -- cgit v1.2.3-54-g00ecf From cd1a1d9638962dbd289088277832f0e817e95b5b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 5 Mar 2018 08:48:04 -0800 Subject: Web needs to know about receive mode, not the OnionShare object --- onionshare/__init__.py | 6 +++++- onionshare/onionshare.py | 5 +---- onionshare/web.py | 13 +++++++++++++ onionshare_gui/__init__.py | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index fe7f72dd..f1252f12 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -67,6 +67,10 @@ def main(cwd=None): print(strings._('no_filenames')) sys.exit() + # Tell web if receive mode is enabled + if receive: + web.set_receive_mode() + # Debug mode? if debug: common.set_debug(debug) @@ -100,7 +104,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(onion, receive, local_only, stay_open, shutdown_timeout) + app = OnionShare(onion, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) app.start_onion_service() except KeyboardInterrupt: diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index a8f2ceb0..85bfaf22 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -27,7 +27,7 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, onion, receive, local_only=False, stay_open=False, shutdown_timeout=0): + def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0): common.log('OnionShare', '__init__') # The Onion object @@ -37,9 +37,6 @@ class OnionShare(object): self.onion_host = None self.stealth = None - # Receiver mode - self.receive = receive - # files and dirs to delete on shutdown self.cleanup_filenames = [] diff --git a/onionshare/web.py b/onionshare/web.py index d16ca251..c7d3fae6 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -175,6 +175,19 @@ def set_gui_mode(): gui_mode = True +# Are we using receive mode? +receive_mode = False + + +def set_receive_mode(): + """ + Tell the web service that we're running in GUI mode + """ + global receive_mode + receive_mode = True + print('receive mode enabled') + + def debug_mode(): """ Turn on debugging mode, which will log flask errors to a debug file. diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 945487fc..24e627bb 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -105,7 +105,7 @@ def main(): # Start the OnionShare app web.set_stay_open(stay_open) - app = OnionShare(onion, False, local_only, stay_open, shutdown_timeout) + app = OnionShare(onion, local_only, stay_open, shutdown_timeout) # Launch the gui gui = OnionShareGui(onion, qtapp, app, filenames, config) -- cgit v1.2.3-54-g00ecf From 08957c5145be6504907760b078045cc42d16b49c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 5 Mar 2018 08:54:20 -0800 Subject: Fix settings test to account for new downloads_dir setting --- test/test_onionshare_settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index e50eee41..acf5cc6a 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -67,7 +67,8 @@ class TestSettings: 'save_private_key': False, 'private_key': '', 'slug': '', - 'hidservauth_string': '' + 'hidservauth_string': '', + 'downloads_dir': os.path.expanduser('~/Downloads') } def test_fill_in_defaults(self, settings_obj): -- cgit v1.2.3-54-g00ecf From 0cec696055cfa76494369709abebad5839180496 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 5 Mar 2018 11:06:59 -0800 Subject: Refactor web.py to move all the web logic into the Web class, and refactor onionshare (cli) to work with it -- but onionshare_gui is currently broken --- onionshare/__init__.py | 13 +- onionshare/web.py | 786 +++++++++++++++++++++------------------------ onionshare_gui/__init__.py | 3 +- 3 files changed, 378 insertions(+), 424 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index f1252f12..8d914f9c 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -20,7 +20,8 @@ along with this program. If not, see . import os, sys, time, argparse, threading -from . import strings, common, web +from . import strings, common +from .web import Web from .onion import * from .onionshare import OnionShare from .settings import Settings @@ -67,14 +68,9 @@ def main(cwd=None): print(strings._('no_filenames')) sys.exit() - # Tell web if receive mode is enabled - if receive: - web.set_receive_mode() - # Debug mode? if debug: common.set_debug(debug) - web.debug_mode() # Validation valid = True @@ -88,10 +84,13 @@ def main(cwd=None): if not valid: sys.exit() - + # Load settings settings = Settings(config) settings.load() + # Create the Web object + web = Web(debug, stay_open, False, receive) + # Start the Onion object onion = Onion() try: diff --git a/onionshare/web.py b/onionshare/web.py index c7d3fae6..3e75ba27 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -37,424 +37,378 @@ from flask import ( from . import strings, common - -def _safe_select_jinja_autoescape(self, filename): - if filename is None: - return True - return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) - -# Starting in Flask 0.11, render_template_string autoescapes template variables -# by default. To prevent content injection through template variables in -# earlier versions of Flask, we force autoescaping in the Jinja2 template -# engine if we detect a Flask version with insecure default behavior. -if Version(flask_version) < Version('0.11'): - # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc - Flask.select_jinja_autoescape = _safe_select_jinja_autoescape - -app = Flask(__name__) - -# information about the file -file_info = [] -zip_filename = None -zip_filesize = None - -security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'), - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') -] - - -def set_file_info(filenames, processed_size_callback=None): - """ - Using the list of filenames being shared, fill in details that the web - page will need to display. This includes zipping up the file in order to - get the zip file's name and size. - """ - global file_info, zip_filename, zip_filesize - - # build file info list - file_info = {'files': [], 'dirs': []} - for filename in filenames: - info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) - } - if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = common.human_readable_filesize(info['size']) - file_info['files'].append(info) - if os.path.isdir(filename): - info['size'] = common.dir_size(filename) - info['size_human'] = common.human_readable_filesize(info['size']) - file_info['dirs'].append(info) - file_info['files'] = sorted(file_info['files'], key=lambda k: k['basename']) - file_info['dirs'] = sorted(file_info['dirs'], key=lambda k: k['basename']) - - # zip up the files and folders - z = common.ZipWriter(processed_size_callback=processed_size_callback) - for info in file_info['files']: - z.add_file(info['filename']) - for info in file_info['dirs']: - z.add_dir(info['filename']) - z.close() - zip_filename = z.zip_filename - zip_filesize = os.path.getsize(zip_filename) - - -REQUEST_LOAD = 0 -REQUEST_DOWNLOAD = 1 -REQUEST_PROGRESS = 2 -REQUEST_OTHER = 3 -REQUEST_CANCELED = 4 -REQUEST_RATE_LIMIT = 5 -q = queue.Queue() - - -def add_request(request_type, path, data=None): - """ - Add a request to the queue, to communicate with the GUI. - """ - global q - q.put({ - 'type': request_type, - 'path': path, - 'data': data - }) - - -# Load and base64 encode images to pass into templates -favicon_b64 = base64.b64encode(open(common.get_resource_path('images/favicon.ico'), 'rb').read()).decode() -logo_b64 = base64.b64encode(open(common.get_resource_path('images/logo.png'), 'rb').read()).decode() -folder_b64 = base64.b64encode(open(common.get_resource_path('images/web_folder.png'), 'rb').read()).decode() -file_b64 = base64.b64encode(open(common.get_resource_path('images/web_file.png'), 'rb').read()).decode() - -slug = None - - -def generate_slug(persistent_slug=''): - global slug - if persistent_slug: - slug = persistent_slug - else: - slug = common.build_slug() - -download_count = 0 -error404_count = 0 - -stay_open = False - - -def set_stay_open(new_stay_open): - """ - Set stay_open variable. - """ - global stay_open - stay_open = new_stay_open - - -def get_stay_open(): - """ - Get stay_open variable. - """ - return stay_open - - -# Are we running in GUI mode? -gui_mode = False - - -def set_gui_mode(): - """ - Tell the web service that we're running in GUI mode - """ - global gui_mode - gui_mode = True - - -# Are we using receive mode? -receive_mode = False - - -def set_receive_mode(): - """ - Tell the web service that we're running in GUI mode - """ - global receive_mode - receive_mode = True - print('receive mode enabled') - - -def debug_mode(): - """ - Turn on debugging mode, which will log flask errors to a debug file. +class Web(object): """ - temp_dir = tempfile.gettempdir() - log_handler = logging.FileHandler( - os.path.join(temp_dir, 'onionshare_server.log')) - log_handler.setLevel(logging.WARNING) - app.logger.addHandler(log_handler) - - -def check_slug_candidate(slug_candidate, slug_compare=None): - if not slug_compare: - slug_compare = slug - if not hmac.compare_digest(slug_compare, slug_candidate): - abort(404) - - -# If "Stop After First Download" is checked (stay_open == False), only allow -# one download at a time. -download_in_progress = False - -done = False - -@app.route("/") -def index(slug_candidate): - """ - Render the template for the onionshare landing page. - """ - check_slug_candidate(slug_candidate) - - add_request(REQUEST_LOAD, request.path) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - global stay_open, download_in_progress - deny_download = not stay_open and download_in_progress - if deny_download: - r = make_response(render_template_string( - open(common.get_resource_path('html/denied.html')).read(), - favicon_b64=favicon_b64 - )) - for header, value in security_headers: - r.headers.set(header, value) - return r - - # If download is allowed to continue, serve download page - - r = make_response(render_template_string( - open(common.get_resource_path('html/index.html')).read(), - favicon_b64=favicon_b64, - logo_b64=logo_b64, - folder_b64=folder_b64, - file_b64=file_b64, - slug=slug, - file_info=file_info, - filename=os.path.basename(zip_filename), - filesize=zip_filesize, - filesize_human=common.human_readable_filesize(zip_filesize))) - for header, value in security_headers: - r.headers.set(header, value) - return r - - -# If the client closes the OnionShare window while a download is in progress, -# it should immediately stop serving the file. The client_cancel global is -# used to tell the download function that the client is canceling the download. -client_cancel = False - - -@app.route("//download") -def download(slug_candidate): - """ - Download the zip file. + The Web object is the OnionShare web server, powered by flask """ - check_slug_candidate(slug_candidate) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - global stay_open, download_in_progress, done - deny_download = not stay_open and download_in_progress - if deny_download: - r = make_response(render_template_string( - open(common.get_resource_path('html/denied.html')).read(), - favicon_b64=favicon_b64 - )) - for header,value in security_headers: - r.headers.set(header, value) - return r - - global download_count - - # each download has a unique id - download_id = download_count - download_count += 1 - - # prepare some variables to use inside generate() function below - # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') - path = request.path - - # tell GUI the download started - add_request(REQUEST_DOWNLOAD, path, {'id': download_id}) - - dirname = os.path.dirname(zip_filename) - basename = os.path.basename(zip_filename) - - def generate(): - # The user hasn't canceled the download - global client_cancel, gui_mode - client_cancel = False - - # Starting a new download - global stay_open, download_in_progress, done - if not stay_open: - download_in_progress = True - - chunk_size = 102400 # 100kb - - fp = open(zip_filename, 'rb') - done = False - canceled = False - while not done: - # The user has canceled the download, so stop serving the file - if client_cancel: - add_request(REQUEST_CANCELED, path, {'id': download_id}) - break - - chunk = fp.read(chunk_size) - if chunk == b'': - done = True - else: - try: - yield chunk - - # tell GUI the progress - downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / zip_filesize) * 100 - - # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - plat = common.get_platform() - if not gui_mode or plat == 'Linux' or plat == 'BSD': - sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent)) - sys.stdout.flush() - - add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) - done = False - except: - # looks like the download was canceled - done = True - canceled = True - - # tell the GUI the download has canceled - add_request(REQUEST_CANCELED, path, {'id': download_id}) - - fp.close() - - if common.get_platform() != 'Darwin': - sys.stdout.write("\n") - - # Download is finished - if not stay_open: - download_in_progress = False - - # Close the server, if necessary - if not stay_open and not canceled: - print(strings._("closing_automatically")) - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() - - r = Response(generate()) - r.headers.set('Content-Length', zip_filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) - for header,value in security_headers: - r.headers.set(header, value) - # guess content type - (content_type, _) = mimetypes.guess_type(basename, strict=False) - if content_type is not None: - r.headers.set('Content-Type', content_type) - return r - - -@app.errorhandler(404) -def page_not_found(e): - """ - 404 error page. - """ - add_request(REQUEST_OTHER, request.path) - - global error404_count - if request.path != '/favicon.ico': - error404_count += 1 - if error404_count == 20: - add_request(REQUEST_RATE_LIMIT, request.path) + def __init__(self, debug, stay_open, gui_mode, receive_mode): + # The flask app + self.app = Flask(__name__) + + # Debug mode? + if debug: + self.debug_mode() + + # Stay open after the first download? + self.stay_open = False + + # Are we running in GUI mode? + self.gui_mode = False + + # Are we using receive mode? + self.receive_mode = False + + + # Starting in Flask 0.11, render_template_string autoescapes template variables + # by default. To prevent content injection through template variables in + # earlier versions of Flask, we force autoescaping in the Jinja2 template + # engine if we detect a Flask version with insecure default behavior. + if Version(flask_version) < Version('0.11'): + # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc + Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape + + # Information about the file + self.file_info = [] + self.zip_filename = None + self.zip_filesize = None + + self.security_headers = [ + ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'), + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'OnionShare') + ] + + self.REQUEST_LOAD = 0 + self.REQUEST_DOWNLOAD = 1 + self.REQUEST_PROGRESS = 2 + self.REQUEST_OTHER = 3 + self.REQUEST_CANCELED = 4 + self.REQUEST_RATE_LIMIT = 5 + self.q = queue.Queue() + + # Load and base64 encode images to pass into templates + self.favicon_b64 = self.base64_image('favicon.ico') + self.logo_b64 = self.base64_image('logo.png') + self.folder_b64 = self.base64_image('web_folder.png') + self.file_b64 = self.base64_image('web_file.png') + + self.slug = None + + self.download_count = 0 + self.error404_count = 0 + + # If "Stop After First Download" is checked (stay_open == False), only allow + # one download at a time. + self.download_in_progress = False + + self.done = False + + # If the client closes the OnionShare window while a download is in progress, + # it should immediately stop serving the file. The client_cancel global is + # used to tell the download function that the client is canceling the download. + self.client_cancel = False + + # shutting down the server only works within the context of flask, so the easiest way to do it is over http + self.shutdown_slug = common.random_string(16) + + @self.app.route("/") + def index(slug_candidate): + """ + Render the template for the onionshare landing page. + """ + self.check_slug_candidate(slug_candidate) + + self.add_request(self.REQUEST_LOAD, request.path) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template_string( + open(common.get_resource_path('html/denied.html')).read(), + favicon_b64=self.favicon_b64 + )) + for header, value in self.security_headers: + r.headers.set(header, value) + return r + + # If download is allowed to continue, serve download page + r = make_response(render_template_string( + open(common.get_resource_path('html/index.html')).read(), + favicon_b64=self.favicon_b64, + logo_b64=self.logo_b64, + folder_b64=self.folder_b64, + file_b64=self.file_b64, + slug=self.slug, + file_info=self.file_info, + filename=os.path.basename(self.zip_filename), + filesize=self.zip_filesize, + filesize_human=common.human_readable_filesize(self.zip_filesize))) + for header, value in self.security_headers: + r.headers.set(header, value) + return r + + @self.app.route("//download") + def download(slug_candidate): + """ + Download the zip file. + """ + self.check_slug_candidate(slug_candidate) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template_string( + open(common.get_resource_path('html/denied.html')).read(), + favicon_b64=self.favicon_b64 + )) + for header,value in self.security_headers: + r.headers.set(header, value) + return r + + # each download has a unique id + download_id = self.download_count + self.download_count += 1 + + # prepare some variables to use inside generate() function below + # which is outside of the request context + shutdown_func = request.environ.get('werkzeug.server.shutdown') + path = request.path + + # tell GUI the download started + self.add_request(self.REQUEST_DOWNLOAD, path, {'id': download_id}) + + dirname = os.path.dirname(self.zip_filename) + basename = os.path.basename(self.zip_filename) + + def generate(): + # The user hasn't canceled the download + self.client_cancel = False + + # Starting a new download + if not self.stay_open: + self.download_in_progress = True + + chunk_size = 102400 # 100kb + + fp = open(self.zip_filename, 'rb') + self.done = False + canceled = False + while not self.done: + # The user has canceled the download, so stop serving the file + if self.client_cancel: + self.add_request(self.REQUEST_CANCELED, path, {'id': download_id}) + break + + chunk = fp.read(chunk_size) + if chunk == b'': + self.done = True + else: + try: + yield chunk + + # tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100 + + # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) + plat = common.get_platform() + if not self.gui_mode or plat == 'Linux' or plat == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) + self.done = False + except: + # looks like the download was canceled + self.done = True + canceled = True + + # tell the GUI the download has canceled + self.add_request(self.REQUEST_CANCELED, path, {'id': download_id}) + + fp.close() + + if common.get_platform() != 'Darwin': + sys.stdout.write("\n") + + # Download is finished + if not self.stay_open: + self.download_in_progress = False + + # Close the server, if necessary + if not self.stay_open and not canceled: + print(strings._("closing_automatically")) + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() + + r = Response(generate()) + r.headers.set('Content-Length', self.zip_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + for header,value in self.security_headers: + r.headers.set(header, value) + # guess content type + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r + + @self.app.errorhandler(404) + def page_not_found(e): + """ + 404 error page. + """ + self.add_request(self.REQUEST_OTHER, request.path) + + if request.path != '/favicon.ico': + self.error404_count += 1 + if self.error404_count == 20: + self.add_request(self.REQUEST_RATE_LIMIT, request.path) + force_shutdown() + print(strings._('error_rate_limit')) + + r = make_response(render_template_string( + open(common.get_resource_path('html/404.html')).read(), + favicon_b64=self.favicon_b64 + ), 404) + for header, value in self.security_headers: + r.headers.set(header, value) + return r + + @self.app.route("//shutdown") + def shutdown(slug_candidate): + """ + Stop the flask web server, from the context of an http request. + """ + check_slug_candidate(slug_candidate, shutdown_slug) force_shutdown() - print(strings._('error_rate_limit')) - - r = make_response(render_template_string( - open(common.get_resource_path('html/404.html')).read(), - favicon_b64=favicon_b64 - ), 404) - for header, value in security_headers: - r.headers.set(header, value) - return r - - -# shutting down the server only works within the context of flask, so the easiest way to do it is over http -shutdown_slug = common.random_string(16) - - -@app.route("//shutdown") -def shutdown(slug_candidate): - """ - Stop the flask web server, from the context of an http request. - """ - check_slug_candidate(slug_candidate, shutdown_slug) - force_shutdown() - return "" - - -def force_shutdown(): - """ - Stop the flask web server, from the context of the flask app. - """ - # shutdown the flask service - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('Not running with the Werkzeug Server') - func() - - -def start(port, stay_open=False, persistent_slug=''): - """ - Start the flask web server. - """ - generate_slug(persistent_slug) - - set_stay_open(stay_open) - - # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) - if os.path.exists('/usr/share/anon-ws-base-files/workstation'): - host = '0.0.0.0' - else: - host = '127.0.0.1' - - app.run(host=host, port=port, threaded=True) - - -def stop(port): - """ - Stop the flask web server by loading /shutdown. - """ - - # If the user cancels the download, let the download function know to stop - # serving the file - global client_cancel - client_cancel = True - - # to stop flask, load http://127.0.0.1://shutdown - try: - s = socket.socket() - s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug)) - except: + return "" + + def set_file_info(self, filenames, processed_size_callback=None): + """ + Using the list of filenames being shared, fill in details that the web + page will need to display. This includes zipping up the file in order to + get the zip file's name and size. + """ + # build file info list + self.file_info = {'files': [], 'dirs': []} + for filename in filenames: + info = { + 'filename': filename, + 'basename': os.path.basename(filename.rstrip('/')) + } + if os.path.isfile(filename): + info['size'] = os.path.getsize(filename) + info['size_human'] = common.human_readable_filesize(info['size']) + self.file_info['files'].append(info) + if os.path.isdir(filename): + info['size'] = common.dir_size(filename) + info['size_human'] = common.human_readable_filesize(info['size']) + self.file_info['dirs'].append(info) + self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) + self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + + # zip up the files and folders + z = common.ZipWriter(processed_size_callback=processed_size_callback) + for info in self.file_info['files']: + z.add_file(info['filename']) + for info in self.file_info['dirs']: + z.add_dir(info['filename']) + z.close() + self.zip_filename = z.zip_filename + self.zip_filesize = os.path.getsize(self.zip_filename) + + def _safe_select_jinja_autoescape(self, filename): + if filename is None: + return True + return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + + def base64_image(self, filename): + """ + Base64-encode an image file to use data URIs in the web app + """ + return base64.b64encode(open(common.get_resource_path('images/{}'.format(filename)), 'rb').read()).decode() + + def add_request(self, request_type, path, data=None): + """ + Add a request to the queue, to communicate with the GUI. + """ + self.q.put({ + 'type': request_type, + 'path': path, + 'data': data + }) + + def generate_slug(self, persistent_slug=''): + if persistent_slug: + self.slug = persistent_slug + else: + self.slug = common.build_slug() + + def debug_mode(self): + """ + Turn on debugging mode, which will log flask errors to a debug file. + """ + temp_dir = tempfile.gettempdir() + log_handler = logging.FileHandler( + os.path.join(temp_dir, 'onionshare_server.log')) + log_handler.setLevel(logging.WARNING) + self.app.logger.addHandler(log_handler) + + def check_slug_candidate(self, slug_candidate, slug_compare=None): + if not slug_compare: + slug_compare = self.slug + if not hmac.compare_digest(slug_compare, slug_candidate): + abort(404) + + def force_shutdown(self): + """ + Stop the flask web server, from the context of the flask app. + """ + # shutdown the flask service + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + + def start(self, port, stay_open=False, persistent_slug=''): + """ + Start the flask web server. + """ + self.generate_slug(persistent_slug) + + self.stay_open = stay_open + + # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) + if os.path.exists('/usr/share/anon-ws-base-files/workstation'): + host = '0.0.0.0' + else: + host = '127.0.0.1' + + self.app.run(host=host, port=port, threaded=True) + + def stop(self, port): + """ + Stop the flask web server by loading /shutdown. + """ + + # If the user cancels the download, let the download function know to stop + # serving the file + self.client_cancel = True + + # to stop flask, load http://127.0.0.1://shutdown try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read() + s = socket.socket() + s.connect(('127.0.0.1', port)) + s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug)) except: - pass + try: + urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read() + except: + pass diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 24e627bb..a40c081f 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -22,7 +22,8 @@ import os, sys, platform, argparse from .alert import Alert from PyQt5 import QtCore, QtWidgets -from onionshare import strings, common, web +from onionshare import strings, common +from .web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare from onionshare.settings import Settings -- cgit v1.2.3-54-g00ecf From 4a0c6e8dcdfb6468020313f38be1496cd6cabdac Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 00:56:40 -0800 Subject: Refactor OnionShareGui to use new Web class --- onionshare/web.py | 9 ++++--- onionshare_gui/__init__.py | 10 ++++---- onionshare_gui/onionshare_gui.py | 51 +++++++++++++++++++++------------------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 3e75ba27..afe66d84 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -41,7 +41,7 @@ class Web(object): """ The Web object is the OnionShare web server, powered by flask """ - def __init__(self, debug, stay_open, gui_mode, receive_mode): + def __init__(self, debug, stay_open, gui_mode, receive_mode=False): # The flask app self.app = Flask(__name__) @@ -50,14 +50,13 @@ class Web(object): self.debug_mode() # Stay open after the first download? - self.stay_open = False + self.stay_open = stay_open # Are we running in GUI mode? - self.gui_mode = False + self.gui_mode = gui_mode # Are we using receive mode? - self.receive_mode = False - + self.receive_mode = receive_mode # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index a40c081f..205f1c82 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -23,7 +23,7 @@ from .alert import Alert from PyQt5 import QtCore, QtWidgets from onionshare import strings, common -from .web import Web +from onionshare.web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare from onionshare.settings import Settings @@ -86,7 +86,6 @@ def main(): # Debug mode? if debug: common.set_debug(debug) - web.debug_mode() # Validation if filenames: @@ -101,15 +100,18 @@ def main(): if not valid: sys.exit() + # Create the Web object + web = Web(debug, stay_open, True) + # Start the Onion onion = Onion() # Start the OnionShare app - web.set_stay_open(stay_open) app = OnionShare(onion, local_only, stay_open, shutdown_timeout) # Launch the gui - gui = OnionShareGui(onion, qtapp, app, filenames, config) + web.stay_open = stay_open + gui = OnionShareGui(web, onion, qtapp, app, filenames, config) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f38dd727..947499ed 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -17,10 +17,13 @@ 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 . """ -import os, threading, time +import os +import threading +import time +import queue from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common, web +from onionshare import strings, common from onionshare.settings import Settings from onionshare.onion import * @@ -43,13 +46,14 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - def __init__(self, onion, qtapp, app, filenames, config=False): + def __init__(self, web, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() self._initSystemTray() common.log('OnionShareGui', '__init__') + self.web = web self.onion = onion self.qtapp = qtapp self.app = app @@ -70,7 +74,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.file_selection.file_list.add_file(filename) # Server status - self.server_status = ServerStatus(self.qtapp, self.app, web, self.file_selection, self.settings) + self.server_status = ServerStatus(self.qtapp, self.app, self.web, self.file_selection, self.settings) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) @@ -377,9 +381,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_share_status_label.setText('') # Reset web counters - web.download_count = 0 - web.error404_count = 0 - web.set_gui_mode() + self.web.download_count = 0 + self.web.error404_count = 0 # start the onion service in a new thread def start_onion_service(self): @@ -395,7 +398,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app.stay_open = not self.settings.get('close_after_first_download') # start onionshare http service in new thread - t = threading.Thread(target=web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash @@ -428,8 +431,8 @@ class OnionShareGui(QtWidgets.QMainWindow): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) try: - web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(web.zip_filename) + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) self.starting_server_step3.emit() # done @@ -455,7 +458,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self._zip_progress_bar = None # warn about sending large files over Tor - if web.zip_filesize >= 157286400: # 150mb + if self.web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() @@ -503,7 +506,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.server_status.status != self.server_status.STATUS_STOPPED: try: - web.stop(self.app.port) + self.web.stop(self.app.port) except: # Probably we had no port to begin with (Onion service didn't start) pass @@ -570,33 +573,33 @@ class OnionShareGui(QtWidgets.QMainWindow): done = False while not done: try: - r = web.q.get(False) + r = self.web.q.get(False) events.append(r) - except web.queue.Empty: + except queue.Empty: done = True for event in events: - if event["type"] == web.REQUEST_LOAD: + if event["type"] == self.web.REQUEST_LOAD: self.status_bar.showMessage(strings._('download_page_loaded', True)) - elif event["type"] == web.REQUEST_DOWNLOAD: + elif event["type"] == self.web.REQUEST_DOWNLOAD: self.downloads_container.show() # show the downloads layout - self.downloads.add_download(event["data"]["id"], web.zip_filesize) + self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) - elif event["type"] == web.REQUEST_RATE_LIMIT: + elif event["type"] == self.web.REQUEST_RATE_LIMIT: self.stop_server() Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) - elif event["type"] == web.REQUEST_PROGRESS: + elif event["type"] == self.web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) # is the download complete? - if event["data"]["bytes"] == web.zip_filesize: + if event["data"]["bytes"] == self.web.zip_filesize: if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info @@ -607,7 +610,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_downloads_in_progress(self.downloads_in_progress) # close on finish? - if not web.get_stay_open(): + if not self.web.stay_open: self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('closing_automatically', True)) @@ -618,7 +621,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_downloads_in_progress(self.downloads_in_progress) - elif event["type"] == web.REQUEST_CANCELED: + elif event["type"] == self.web.REQUEST_CANCELED: self.downloads.cancel_download(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 @@ -627,7 +630,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: @@ -638,7 +641,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) if not self.app.shutdown_timer.is_alive(): # If there were no attempts to download the share, or all downloads are done, we can stop - if web.download_count == 0 or web.done: + if self.web.download_count == 0 or self.web.done: self.server_status.stop_server() self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('close_on_timeout', True)) -- cgit v1.2.3-54-g00ecf From fa9f71465142fa9b867f94a3cdab5c856061fd17 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 01:50:43 -0800 Subject: Make separate routes for send and receive modes --- onionshare/web.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/onionshare/web.py b/onionshare/web.py index afe66d84..2814616d 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -113,6 +113,17 @@ class Web(object): # shutting down the server only works within the context of flask, so the easiest way to do it is over http self.shutdown_slug = common.random_string(16) + # Define the ewb app routes + self.common_routes() + if self.receive_mode: + self.receive_routes() + else: + self.send_routes() + + def send_routes(self): + """ + The web app routes for sharing files + """ @self.app.route("/") def index(slug_candidate): """ @@ -258,6 +269,19 @@ class Web(object): r.headers.set('Content-Type', content_type) return r + def receive_routes(self): + """ + The web app routes for sharing files + """ + @self.app.route("/") + def index(slug_candidate): + return "Receive Mode" + + + def common_routes(self): + """ + Common web app routes between sending and receiving + """ @self.app.errorhandler(404) def page_not_found(e): """ -- cgit v1.2.3-54-g00ecf From baede536321431d5a19a2bb3ae659e875c2d9c63 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 02:06:44 -0800 Subject: Make separate template for send and receive mode --- onionshare/web.py | 20 +++-- share/html/index.html | 208 ------------------------------------------------ share/html/receive.html | 105 ++++++++++++++++++++++++ share/html/send.html | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 213 deletions(-) delete mode 100644 share/html/index.html create mode 100644 share/html/receive.html create mode 100644 share/html/send.html diff --git a/onionshare/web.py b/onionshare/web.py index 2814616d..7e488a86 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -147,7 +147,7 @@ class Web(object): # If download is allowed to continue, serve download page r = make_response(render_template_string( - open(common.get_resource_path('html/index.html')).read(), + open(common.get_resource_path('html/send.html')).read(), favicon_b64=self.favicon_b64, logo_b64=self.logo_b64, folder_b64=self.folder_b64, @@ -275,7 +275,17 @@ class Web(object): """ @self.app.route("/") def index(slug_candidate): - return "Receive Mode" + self.check_slug_candidate(slug_candidate) + + # If download is allowed to continue, serve download page + r = make_response(render_template_string( + open(common.get_resource_path('html/receive.html')).read(), + favicon_b64=self.favicon_b64, + logo_b64=self.logo_b64, + slug=self.slug)) + for header, value in self.security_headers: + r.headers.set(header, value) + return r def common_routes(self): @@ -293,7 +303,7 @@ class Web(object): self.error404_count += 1 if self.error404_count == 20: self.add_request(self.REQUEST_RATE_LIMIT, request.path) - force_shutdown() + self.force_shutdown() print(strings._('error_rate_limit')) r = make_response(render_template_string( @@ -309,8 +319,8 @@ class Web(object): """ Stop the flask web server, from the context of an http request. """ - check_slug_candidate(slug_candidate, shutdown_slug) - force_shutdown() + self.check_slug_candidate(slug_candidate, shutdown_slug) + self.force_shutdown() return "" def set_file_info(self, filenames, processed_size_callback=None): diff --git a/share/html/index.html b/share/html/index.html deleted file mode 100644 index 57711e02..00000000 --- a/share/html/index.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - OnionShare - - - - - - - -
-
- -
- -

OnionShare

-
- - - - - - - - {% for info in file_info.dirs %} - - - - - - {% endfor %} - {% for info in file_info.files %} - - - - - - {% endfor %} -
FilenameSize
- - {{ info.basename }} - {{ info.size_human }}
- - {{ info.basename }} - {{ info.size_human }}
- - - diff --git a/share/html/receive.html b/share/html/receive.html new file mode 100644 index 00000000..942ec62e --- /dev/null +++ b/share/html/receive.html @@ -0,0 +1,105 @@ + + + + OnionShare + + + + + +
+ +

OnionShare

+
+ + + diff --git a/share/html/send.html b/share/html/send.html new file mode 100644 index 00000000..57711e02 --- /dev/null +++ b/share/html/send.html @@ -0,0 +1,208 @@ + + + + OnionShare + + + + + + + +
+
+ +
+ +

OnionShare

+
+ + + + + + + + {% for info in file_info.dirs %} + + + + + + {% endfor %} + {% for info in file_info.files %} + + + + + + {% endfor %} +
FilenameSize
+ + {{ info.basename }} + {{ info.size_human }}
+ + {{ info.basename }} + {{ info.size_human }}
+ + + -- cgit v1.2.3-54-g00ecf From ce852fc60a3c4d7a0b826d3f01da9fcd00302b93 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 02:54:12 -0800 Subject: Create separate templates and static folder, and make the web app use both of these. Yay, now we have real static resources --- MANIFEST.in | 3 +- install/pyinstaller.spec | 3 +- onionshare/web.py | 80 ++++++----------- setup.py | 3 +- share/html/404.html | 16 ---- share/html/denied.html | 19 ---- share/html/receive.html | 105 ---------------------- share/html/send.html | 208 ------------------------------------------- share/images/favicon.ico | Bin 4286 -> 0 bytes share/images/web_file.png | Bin 251 -> 0 bytes share/images/web_folder.png | Bin 338 -> 0 bytes share/static/favicon.ico | Bin 0 -> 4286 bytes share/static/logo.png | Bin 0 -> 3824 bytes share/static/web_file.png | Bin 0 -> 251 bytes share/static/web_folder.png | Bin 0 -> 338 bytes share/templates/404.html | 16 ++++ share/templates/denied.html | 19 ++++ share/templates/receive.html | 105 ++++++++++++++++++++++ share/templates/send.html | 208 +++++++++++++++++++++++++++++++++++++++++++ 19 files changed, 379 insertions(+), 406 deletions(-) delete mode 100644 share/html/404.html delete mode 100644 share/html/denied.html delete mode 100644 share/html/receive.html delete mode 100644 share/html/send.html delete mode 100644 share/images/favicon.ico delete mode 100644 share/images/web_file.png delete mode 100644 share/images/web_folder.png create mode 100644 share/static/favicon.ico create mode 100644 share/static/logo.png create mode 100644 share/static/web_file.png create mode 100644 share/static/web_folder.png create mode 100644 share/templates/404.html create mode 100644 share/templates/denied.html create mode 100644 share/templates/receive.html create mode 100644 share/templates/send.html diff --git a/MANIFEST.in b/MANIFEST.in index f4d1c078..c8a4d87c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,7 +4,8 @@ include BUILD.md include share/* include share/images/* include share/locale/* -include share/html/* +include share/templates/* +include share/static/* include install/onionshare.desktop include install/onionshare.appdata.xml include install/onionshare80.xpm diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index 6ca2fdbe..a4f1532a 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -20,7 +20,8 @@ a = Analysis( ('../share/torrc_template-windows', 'share'), ('../share/images/*', 'share/images'), ('../share/locale/*', 'share/locale'), - ('../share/html/*', 'share/html') + ('../share/templates/*', 'share/templates'), + ('../share/static/*', 'share/static') ], hiddenimports=[], hookspath=[], diff --git a/onionshare/web.py b/onionshare/web.py index 7e488a86..5d1d0cb8 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -26,12 +26,11 @@ import queue import socket import sys import tempfile -import base64 from distutils.version import LooseVersion as Version from urllib.request import urlopen from flask import ( - Flask, Response, request, render_template_string, abort, make_response, + Flask, Response, request, render_template, abort, make_response, __version__ as flask_version ) @@ -43,7 +42,9 @@ class Web(object): """ def __init__(self, debug, stay_open, gui_mode, receive_mode=False): # The flask app - self.app = Flask(__name__) + self.app = Flask(__name__, + static_folder=common.get_resource_path('static'), + template_folder=common.get_resource_path('templates')) # Debug mode? if debug: @@ -88,12 +89,6 @@ class Web(object): self.REQUEST_RATE_LIMIT = 5 self.q = queue.Queue() - # Load and base64 encode images to pass into templates - self.favicon_b64 = self.base64_image('favicon.ico') - self.logo_b64 = self.base64_image('logo.png') - self.folder_b64 = self.base64_image('web_folder.png') - self.file_b64 = self.base64_image('web_file.png') - self.slug = None self.download_count = 0 @@ -137,29 +132,18 @@ class Web(object): # currently a download deny_download = not self.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template_string( - open(common.get_resource_path('html/denied.html')).read(), - favicon_b64=self.favicon_b64 - )) - for header, value in self.security_headers: - r.headers.set(header, value) - return r + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) # If download is allowed to continue, serve download page - r = make_response(render_template_string( - open(common.get_resource_path('html/send.html')).read(), - favicon_b64=self.favicon_b64, - logo_b64=self.logo_b64, - folder_b64=self.folder_b64, - file_b64=self.file_b64, + r = make_response(render_template( + 'send.html', slug=self.slug, file_info=self.file_info, filename=os.path.basename(self.zip_filename), filesize=self.zip_filesize, filesize_human=common.human_readable_filesize(self.zip_filesize))) - for header, value in self.security_headers: - r.headers.set(header, value) - return r + return self.add_security_headers(r) @self.app.route("//download") def download(slug_candidate): @@ -172,13 +156,8 @@ class Web(object): # currently a download deny_download = not self.stay_open and self.download_in_progress if deny_download: - r = make_response(render_template_string( - open(common.get_resource_path('html/denied.html')).read(), - favicon_b64=self.favicon_b64 - )) - for header,value in self.security_headers: - r.headers.set(header, value) - return r + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) # each download has a unique id download_id = self.download_count @@ -261,8 +240,7 @@ class Web(object): r = Response(generate()) r.headers.set('Content-Length', self.zip_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) - for header,value in self.security_headers: - r.headers.set(header, value) + r = self.add_security_headers(r) # guess content type (content_type, _) = mimetypes.guess_type(basename, strict=False) if content_type is not None: @@ -278,15 +256,10 @@ class Web(object): self.check_slug_candidate(slug_candidate) # If download is allowed to continue, serve download page - r = make_response(render_template_string( - open(common.get_resource_path('html/receive.html')).read(), - favicon_b64=self.favicon_b64, - logo_b64=self.logo_b64, + r = make_response(render_template( + 'receive.html', slug=self.slug)) - for header, value in self.security_headers: - r.headers.set(header, value) - return r - + return self.add_security_headers(r) def common_routes(self): """ @@ -306,13 +279,8 @@ class Web(object): self.force_shutdown() print(strings._('error_rate_limit')) - r = make_response(render_template_string( - open(common.get_resource_path('html/404.html')).read(), - favicon_b64=self.favicon_b64 - ), 404) - for header, value in self.security_headers: - r.headers.set(header, value) - return r + r = make_response(render_template('404.html'), 404) + return self.add_security_headers(r) @self.app.route("//shutdown") def shutdown(slug_candidate): @@ -323,6 +291,14 @@ class Web(object): self.force_shutdown() return "" + def add_security_headers(self, r): + """ + Add security headers to a request + """ + for header, value in self.security_headers: + r.headers.set(header, value) + return r + def set_file_info(self, filenames, processed_size_callback=None): """ Using the list of filenames being shared, fill in details that the web @@ -362,12 +338,6 @@ class Web(object): return True return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) - def base64_image(self, filename): - """ - Base64-encode an image file to use data URIs in the web app - """ - return base64.b64encode(open(common.get_resource_path('images/{}'.format(filename)), 'rb').read()).decode() - def add_request(self, request_type, path, data=None): """ Add a request to the queue, to communicate with the GUI. diff --git a/setup.py b/setup.py index 23e1ea17..99222ef0 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,8 @@ data_files=[ (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')), - (os.path.join(sys.prefix, 'share/onionshare/html'), file_list('share/html')), + (os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')), + (os.path.join(sys.prefix, 'share/onionshare/static'), file_list('share/static')) ] if platform.system() != 'OpenBSD': data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])) diff --git a/share/html/404.html b/share/html/404.html deleted file mode 100644 index 09d0fc3c..00000000 --- a/share/html/404.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - Error 404 - - - - 404 - diff --git a/share/html/denied.html b/share/html/denied.html deleted file mode 100644 index a82ac027..00000000 --- a/share/html/denied.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - OnionShare - - - - -

OnionShare download in progress

- - diff --git a/share/html/receive.html b/share/html/receive.html deleted file mode 100644 index 942ec62e..00000000 --- a/share/html/receive.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - OnionShare - - - - - -
- -

OnionShare

-
- - - diff --git a/share/html/send.html b/share/html/send.html deleted file mode 100644 index 57711e02..00000000 --- a/share/html/send.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - OnionShare - - - - - - - -
-
- -
- -

OnionShare

-
- - - - - - - - {% for info in file_info.dirs %} - - - - - - {% endfor %} - {% for info in file_info.files %} - - - - - - {% endfor %} -
FilenameSize
- - {{ info.basename }} - {{ info.size_human }}
- - {{ info.basename }} - {{ info.size_human }}
- - - diff --git a/share/images/favicon.ico b/share/images/favicon.ico deleted file mode 100644 index 63e65d8b..00000000 Binary files a/share/images/favicon.ico and /dev/null differ diff --git a/share/images/web_file.png b/share/images/web_file.png deleted file mode 100644 index 1931aff0..00000000 Binary files a/share/images/web_file.png and /dev/null differ diff --git a/share/images/web_folder.png b/share/images/web_folder.png deleted file mode 100644 index 3ca5df21..00000000 Binary files a/share/images/web_folder.png and /dev/null differ diff --git a/share/static/favicon.ico b/share/static/favicon.ico new file mode 100644 index 00000000..63e65d8b Binary files /dev/null and b/share/static/favicon.ico differ diff --git a/share/static/logo.png b/share/static/logo.png new file mode 100644 index 00000000..43884c1f Binary files /dev/null and b/share/static/logo.png differ diff --git a/share/static/web_file.png b/share/static/web_file.png new file mode 100644 index 00000000..1931aff0 Binary files /dev/null and b/share/static/web_file.png differ diff --git a/share/static/web_folder.png b/share/static/web_folder.png new file mode 100644 index 00000000..3ca5df21 Binary files /dev/null and b/share/static/web_folder.png differ diff --git a/share/templates/404.html b/share/templates/404.html new file mode 100644 index 00000000..ce70597f --- /dev/null +++ b/share/templates/404.html @@ -0,0 +1,16 @@ + + + + Error 404 + + + + 404 + diff --git a/share/templates/denied.html b/share/templates/denied.html new file mode 100644 index 00000000..7535e0b4 --- /dev/null +++ b/share/templates/denied.html @@ -0,0 +1,19 @@ + + + + OnionShare + + + + +

OnionShare download in progress

+ + diff --git a/share/templates/receive.html b/share/templates/receive.html new file mode 100644 index 00000000..633991a8 --- /dev/null +++ b/share/templates/receive.html @@ -0,0 +1,105 @@ + + + + OnionShare + + + + + +
+ +

OnionShare

+
+ + + diff --git a/share/templates/send.html b/share/templates/send.html new file mode 100644 index 00000000..1814d214 --- /dev/null +++ b/share/templates/send.html @@ -0,0 +1,208 @@ + + + + OnionShare + + + + + + + +
+
+ +
+ +

OnionShare

+
+ + + + + + + + {% for info in file_info.dirs %} + + + + + + {% endfor %} + {% for info in file_info.files %} + + + + + + {% endfor %} +
FilenameSize
+ + {{ info.basename }} + {{ info.size_human }}
+ + {{ info.basename }} + {{ info.size_human }}
+ + + -- cgit v1.2.3-54-g00ecf From 4606a3ad3aac2d33ffb18e5d727f25fd8ce80d87 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 03:03:24 -0800 Subject: Stop using inline css, and reorganize static folder --- onionshare/web.py | 2 +- share/static/css/404.css | 6 +++ share/static/css/denied.css | 7 +++ share/static/css/style.css | 88 +++++++++++++++++++++++++++++++++++ share/static/favicon.ico | Bin 4286 -> 0 bytes share/static/img/favicon.ico | Bin 0 -> 4286 bytes share/static/img/logo.png | Bin 0 -> 3824 bytes share/static/img/web_file.png | Bin 0 -> 251 bytes share/static/img/web_folder.png | Bin 0 -> 338 bytes share/static/logo.png | Bin 3824 -> 0 bytes share/static/web_file.png | Bin 251 -> 0 bytes share/static/web_folder.png | Bin 338 -> 0 bytes share/templates/404.html | 11 +---- share/templates/denied.html | 12 +---- share/templates/receive.html | 95 ++------------------------------------ share/templates/send.html | 99 ++-------------------------------------- 16 files changed, 114 insertions(+), 206 deletions(-) create mode 100644 share/static/css/404.css create mode 100644 share/static/css/denied.css create mode 100644 share/static/css/style.css delete mode 100644 share/static/favicon.ico create mode 100644 share/static/img/favicon.ico create mode 100644 share/static/img/logo.png create mode 100644 share/static/img/web_file.png create mode 100644 share/static/img/web_folder.png delete mode 100644 share/static/logo.png delete mode 100644 share/static/web_file.png delete mode 100644 share/static/web_folder.png diff --git a/onionshare/web.py b/onionshare/web.py index 5d1d0cb8..3fa9ed7b 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -73,7 +73,7 @@ class Web(object): self.zip_filesize = None self.security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'unsafe-inline\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'), + ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'), ('X-Frame-Options', 'DENY'), ('X-Xss-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), diff --git a/share/static/css/404.css b/share/static/css/404.css new file mode 100644 index 00000000..e8f377ca --- /dev/null +++ b/share/static/css/404.css @@ -0,0 +1,6 @@ +body { + background-color: #FFC4D5; + color: #FF0048; + text-align: center; + font-size: 20em; +} diff --git a/share/static/css/denied.css b/share/static/css/denied.css new file mode 100644 index 00000000..260a3dab --- /dev/null +++ b/share/static/css/denied.css @@ -0,0 +1,7 @@ +body { + background-color: #222222; + color: #ffffff; + text-align: center; + font-family: sans-serif; + padding: 5em 1em; +} diff --git a/share/static/css/style.css b/share/static/css/style.css new file mode 100644 index 00000000..dc20f24d --- /dev/null +++ b/share/static/css/style.css @@ -0,0 +1,88 @@ +.clearfix:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} + +body { + margin: 0; + font-family: Helvetica; +} + +header { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + background: #fcfcfc; + background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%); + padding: 0.8rem; +} + +header .logo { + vertical-align: middle; + width: 45px; + height: 45px; +} + +header h1 { + display: inline-block; + margin: 0 0 0 0.5rem; + vertical-align: middle; + font-weight: normal; + font-size: 1.5rem; + color: #666666; +} + +header .right { + float: right; + font-size: .75rem; +} + +header .right ul li { + display: inline; + margin: 0 0 0 .5rem; + font-size: 1rem; +} + +header .button { + color: #ffffff; + background-color: #4e064f; + padding: 10px; + border-radius: 5px; + text-decoration: none; + margin-left: 1rem; + cursor: pointer; +} + +table.file-list { + width: 100%; + margin: 0 auto; + border-collapse: collapse; +} + +table.file-list th { + text-align: left; + text-transform: uppercase; + font-weight: normal; + color: #666666; + padding: 0.5rem; +} + +table.file-list tr { + border-bottom: 1px solid #e0e0e0; +} + +table.file-list td { + white-space: nowrap; + padding: 0.5rem 10rem 0.5rem 0.8rem; +} + +table.file-list td img { + vertical-align: middle; + margin-right: 0.5rem; +} + +table.file-list td:last-child { + width: 100%; +} diff --git a/share/static/favicon.ico b/share/static/favicon.ico deleted file mode 100644 index 63e65d8b..00000000 Binary files a/share/static/favicon.ico and /dev/null differ diff --git a/share/static/img/favicon.ico b/share/static/img/favicon.ico new file mode 100644 index 00000000..63e65d8b Binary files /dev/null and b/share/static/img/favicon.ico differ diff --git a/share/static/img/logo.png b/share/static/img/logo.png new file mode 100644 index 00000000..43884c1f Binary files /dev/null and b/share/static/img/logo.png differ diff --git a/share/static/img/web_file.png b/share/static/img/web_file.png new file mode 100644 index 00000000..1931aff0 Binary files /dev/null and b/share/static/img/web_file.png differ diff --git a/share/static/img/web_folder.png b/share/static/img/web_folder.png new file mode 100644 index 00000000..3ca5df21 Binary files /dev/null and b/share/static/img/web_folder.png differ diff --git a/share/static/logo.png b/share/static/logo.png deleted file mode 100644 index 43884c1f..00000000 Binary files a/share/static/logo.png and /dev/null differ diff --git a/share/static/web_file.png b/share/static/web_file.png deleted file mode 100644 index 1931aff0..00000000 Binary files a/share/static/web_file.png and /dev/null differ diff --git a/share/static/web_folder.png b/share/static/web_folder.png deleted file mode 100644 index 3ca5df21..00000000 Binary files a/share/static/web_folder.png and /dev/null differ diff --git a/share/templates/404.html b/share/templates/404.html index ce70597f..1cf9d7a2 100644 --- a/share/templates/404.html +++ b/share/templates/404.html @@ -2,15 +2,8 @@ Error 404 - - + + 404 diff --git a/share/templates/denied.html b/share/templates/denied.html index 7535e0b4..39974639 100644 --- a/share/templates/denied.html +++ b/share/templates/denied.html @@ -2,16 +2,8 @@ OnionShare - - + +

OnionShare download in progress

diff --git a/share/templates/receive.html b/share/templates/receive.html index 633991a8..42fe35d2 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -2,102 +2,13 @@ OnionShare - - + +
- +

OnionShare

diff --git a/share/templates/send.html b/share/templates/send.html index 1814d214..07bd76bb 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -2,97 +2,8 @@ OnionShare - - + + @@ -105,7 +16,7 @@
  • Download Files
  • - +

    OnionShare

    @@ -118,7 +29,7 @@ {% for info in file_info.dirs %} - + {{ info.basename }} {{ info.size_human }} @@ -128,7 +39,7 @@ {% for info in file_info.files %} - + {{ info.basename }} {{ info.size_human }} -- cgit v1.2.3-54-g00ecf From 649afa2fad6ad2a8a016ac633de8b4376cade016 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 03:17:54 -0800 Subject: Move (optional) javascript into file, and use CSP to ban inline js --- onionshare/web.py | 2 +- share/static/js/send.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++ share/templates/send.html | 73 ++------------------------------------------- 3 files changed, 79 insertions(+), 71 deletions(-) create mode 100644 share/static/js/send.js diff --git a/onionshare/web.py b/onionshare/web.py index 3fa9ed7b..68684651 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -73,7 +73,7 @@ class Web(object): self.zip_filesize = None self.security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'unsafe-inline\'; img-src \'self\' data:;'), + ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), ('X-Frame-Options', 'DENY'), ('X-Xss-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), diff --git a/share/static/js/send.js b/share/static/js/send.js new file mode 100644 index 00000000..43e9892d --- /dev/null +++ b/share/static/js/send.js @@ -0,0 +1,75 @@ +// Function to convert human-readable sizes back to bytes, for sorting +function unhumanize(text) { + var powers = {'b': 0, 'k': 1, 'm': 2, 'g': 3, 't': 4}; + var regex = /(\d+(?:\.\d+)?)\s?(B|K|M|G|T)?/i; + var res = regex.exec(text); + if(res[2] === undefined) { + // Account for alphabetical words (file/dir names) + return text; + } else { + return res[1] * Math.pow(1024, powers[res[2].toLowerCase()]); + } +} +function sortTable(n) { + var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0; + table = document.getElementById("file-list"); + switching = true; + // Set the sorting direction to ascending: + dir = "asc"; + /* Make a loop that will continue until + no switching has been done: */ + while (switching) { + // Start by saying: no switching is done: + switching = false; + rows = table.getElementsByTagName("TR"); + /* Loop through all table rows (except the + first, which contains table headers): */ + for (i = 1; i < (rows.length - 1); i++) { + // Start by saying there should be no switching: + shouldSwitch = false; + /* Get the two elements you want to compare, + one from current row and one from the next: */ + x = rows[i].getElementsByTagName("TD")[n]; + y = rows[i + 1].getElementsByTagName("TD")[n]; + /* Check if the two rows should switch place, + based on the direction, asc or desc: */ + if (dir == "asc") { + if (unhumanize(x.innerHTML.toLowerCase()) > unhumanize(y.innerHTML.toLowerCase())) { + // If so, mark as a switch and break the loop: + shouldSwitch= true; + break; + } + } else if (dir == "desc") { + if (unhumanize(x.innerHTML.toLowerCase()) < unhumanize(y.innerHTML.toLowerCase())) { + // If so, mark as a switch and break the loop: + shouldSwitch= true; + break; + } + } + } + if (shouldSwitch) { + /* If a switch has been marked, make the switch + and mark that a switch has been done: */ + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + // Each time a switch is done, increase this count by 1: + switchcount ++; + } else { + /* If no switching has been done AND the direction is "asc", + set the direction to "desc" and run the while loop again. */ + if (switchcount == 0 && dir == "asc") { + dir = "desc"; + switching = true; + } + } + } +} + + +// Set click handlers +document.getElementById("filename-header").addEventListener("click", function(){ + sortTable(0); +}); +document.getElementById("size-header").addEventListener("click", function(){ + sortTable(1); +}); diff --git a/share/templates/send.html b/share/templates/send.html index 07bd76bb..ba43f306 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -22,8 +22,8 @@ - - + + {% for info in file_info.dirs %} @@ -47,73 +47,6 @@ {% endfor %}
    FilenameSizeFilenameSize
    - + -- cgit v1.2.3-54-g00ecf From 5d42e76eb830bb16c2a287503a642a71393bf449 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 03:24:17 -0800 Subject: Show different message for receive mode than for send mode --- onionshare/__init__.py | 22 ++++++++++++++++------ share/locale/en.json | 6 ++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 8d914f9c..3ab5cfed 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -144,13 +144,23 @@ def main(cwd=None): settings.set('slug', web.slug) settings.save() - if(stealth): - print(strings._("give_this_url_stealth")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) - print(app.auth_string) + print('') + if receive: + if stealth: + print(strings._("give_this_url_receive_stealth")) + print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(app.auth_string) + else: + print(strings._("give_this_url_receive")) + print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) else: - print(strings._("give_this_url")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + if stealth: + print(strings._("give_this_url_stealth")) + print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(app.auth_string) + else: + print(strings._("give_this_url")) + print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) print('') print(strings._("ctrlc_to_stop")) diff --git a/share/locale/en.json b/share/locale/en.json index e72686b8..8686ea13 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -5,8 +5,10 @@ "wait_for_hs_trying": "Trying...", "wait_for_hs_nope": "Not ready yet.", "wait_for_hs_yup": "Ready!", - "give_this_url": "Give this address to the person you're sending the file to:", - "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", + "give_this_url": "Give this address to the person you're sending the files to:", + "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the files to:", + "give_this_url_receive": "Give this address to the people sending you files:", + "give_this_url_receive_stealth": "Give this address and HidServAuth line to the people sending you files:", "ctrlc_to_stop": "Press Ctrl-C to stop server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", -- cgit v1.2.3-54-g00ecf From e980bc153b601adc129cb7da0101947b2c828691 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 05:25:49 -0800 Subject: Change default receive mode download directory to ~/OnionShare --- onionshare/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 5ec2d2ae..9b61beaf 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -104,7 +104,7 @@ class Settings(object): """ # TODO: Test in Windows, though it looks like it should work # https://docs.python.org/3/library/os.path.html#os.path.expanduser - return os.path.expanduser('~/Downloads') + return os.path.expanduser('~/OnionShare') def load(self): """ -- cgit v1.2.3-54-g00ecf From 878dd4f880507f0e453988eca10e2ead4340b583 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 6 Mar 2018 07:40:57 -0800 Subject: In CLI, validate downloads_dir when starting in receive mode --- onionshare/__init__.py | 38 +++++++++++++++++++++++++++----------- share/locale/en.json | 4 +++- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 3ab5cfed..ec008f5d 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -72,22 +72,38 @@ def main(cwd=None): if debug: common.set_debug(debug) - # Validation - valid = True - for filename in filenames: - if not os.path.isfile(filename) and not os.path.isdir(filename): - print(strings._("not_a_file").format(filename)) - valid = False - if not os.access(filename, os.R_OK): - print(strings._("not_a_readable_file").format(filename)) - valid = False - if not valid: - sys.exit() + # Validate filenames + if not receive: + valid = True + for filename in filenames: + if not os.path.isfile(filename) and not os.path.isdir(filename): + print(strings._("not_a_file").format(filename)) + valid = False + if not os.access(filename, os.R_OK): + print(strings._("not_a_readable_file").format(filename)) + valid = False + if not valid: + sys.exit() # Load settings settings = Settings(config) settings.load() + # In receive mode, validate downloads dir + if receive: + valid = True + if not os.path.isdir(settings.get('downloads_dir')): + try: + os.mkdir(settings.get('downloads_dir'), 0o700) + except: + print(strings._('error_cannot_create_downloads_dir').format(settings.get('downloads_dir'))) + valid = False + if valid and not os.access(settings.get('downloads_dir'), os.W_OK): + print(strings._('error_downloads_dir_not_writable').format(settings.get('downloads_dir'))) + valid = False + if not valid: + sys.exit() + # Create the Web object web = Web(debug, stay_open, False, receive) diff --git a/share/locale/en.json b/share/locale/en.json index 8686ea13..3340afd4 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -156,5 +156,7 @@ "gui_file_info": "{} Files, {}", "gui_file_info_single": "{} File, {}", "info_in_progress_downloads_tooltip": "{} download(s) in progress", - "info_completed_downloads_tooltip": "{} download(s) completed" + "info_completed_downloads_tooltip": "{} download(s) completed", + "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", + "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}" } -- cgit v1.2.3-54-g00ecf From 46a9a2a0a1882c38b00e6eff1d3e1877c46286f3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 7 Mar 2018 16:13:22 +1100 Subject: #662 Allow local-only mode in GUI --- onionshare_gui/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 44 ++++++++++---------- onionshare_gui/settings_dialog.py | 84 +++++++++++++++++++++------------------ 3 files changed, 70 insertions(+), 60 deletions(-) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 24e627bb..c1d84440 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -108,7 +108,7 @@ def main(): app = OnionShare(onion, local_only, stay_open, shutdown_timeout) # Launch the gui - gui = OnionShareGui(onion, qtapp, app, filenames, config) + gui = OnionShareGui(onion, qtapp, app, filenames, config, local_only) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 7dd641dd..0f404ff0 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -43,7 +43,7 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - def __init__(self, onion, qtapp, app, filenames, config=False): + def __init__(self, onion, qtapp, app, filenames, config=False, local_only=False): super(OnionShareGui, self).__init__() self._initSystemTray() @@ -53,6 +53,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.onion = onion self.qtapp = qtapp self.app = app + self.local_only = local_only self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) @@ -219,7 +220,8 @@ class OnionShareGui(QtWidgets.QMainWindow): tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) - tor_con.start() + if not self.local_only: + tor_con.start() # Start the timer self.timer.start(500) @@ -339,19 +341,20 @@ class OnionShareGui(QtWidgets.QMainWindow): # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. - if self.onion.is_authenticated(): - if not self.timer.isActive(): - self.timer.start(500) - # If there were some files listed for sharing, we should be ok to - # re-enable the 'Start Sharing' button now. - if self.server_status.file_selection.get_num_files() > 0: - self.server_status.server_button.setEnabled(True) - self.status_bar.clearMessage() + if not self.local_only: + if self.onion.is_authenticated(): + if not self.timer.isActive(): + self.timer.start(500) + # If there were some files listed for sharing, we should be ok to + # re-enable the 'Start Sharing' button now. + if self.server_status.file_selection.get_num_files() > 0: + self.server_status.server_button.setEnabled(True) + self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() - d = SettingsDialog(self.onion, self.qtapp, self.config) + d = SettingsDialog(self.onion, self.qtapp, self.config, self.local_only) d.settings_saved.connect(reload_settings) d.exec_() @@ -549,15 +552,16 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.update() - # Have we lost connection to Tor somehow? - if not self.onion.is_authenticated(): - self.timer.stop() - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() - self.server_status.server_button.setEnabled(False) - self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) + if not self.local_only: + # Have we lost connection to Tor somehow? + if not self.onion.is_authenticated(): + self.timer.stop() + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.server_status.server_button.setEnabled(False) + self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) + if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane # if a new download has been added diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7c81afc6..15d0251a 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -34,13 +34,14 @@ class SettingsDialog(QtWidgets.QDialog): """ settings_saved = QtCore.pyqtSignal() - def __init__(self, onion, qtapp, config=False): + def __init__(self, onion, qtapp, config=False, local_only=False): super(SettingsDialog, self).__init__() common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp self.config = config + self.local_only = local_only self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) @@ -671,48 +672,52 @@ class SettingsDialog(QtWidgets.QDialog): # If Tor isn't connected, or if Tor settings have changed, Reinitialize # the Onion object reboot_onion = False - if self.onion.is_authenticated(): - common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - def changed(s1, s2, keys): - """ - Compare the Settings objects s1 and s2 and return true if any values - have changed for the given keys. - """ - for key in keys: - if s1.get(key) != s2.get(key): - return True - return False - - if changed(settings, self.old_settings, [ - 'connection_type', 'control_port_address', - 'control_port_port', 'socks_address', 'socks_port', - 'socket_file_path', 'auth_type', 'auth_password', - 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', - 'tor_bridges_use_custom_bridges']): - + if not self.local_only: + if self.onion.is_authenticated(): + common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + def changed(s1, s2, keys): + """ + Compare the Settings objects s1 and s2 and return true if any values + have changed for the given keys. + """ + for key in keys: + if s1.get(key) != s2.get(key): + return True + return False + + if changed(settings, self.old_settings, [ + 'connection_type', 'control_port_address', + 'control_port_port', 'socks_address', 'socks_port', + 'socket_file_path', 'auth_type', 'auth_password', + 'no_bridges', 'tor_bridges_use_obfs4', + 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', + 'tor_bridges_use_custom_bridges']): + + reboot_onion = True + + else: + common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + # Tor isn't connected, so try connecting reboot_onion = True - else: - common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') - # Tor isn't connected, so try connecting - reboot_onion = True + # Do we need to reinitialize Tor? + if reboot_onion: + # Reinitialize the Onion object + common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.onion.cleanup() - # Do we need to reinitialize Tor? - if reboot_onion: - # Reinitialize the Onion object - common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') - self.onion.cleanup() + tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con.start() - tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) - tor_con.start() + common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) - common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + if self.onion.is_authenticated() and not tor_con.wasCanceled(): + self.settings_saved.emit() + self.close() - if self.onion.is_authenticated() and not tor_con.wasCanceled(): + else: self.settings_saved.emit() self.close() - else: self.settings_saved.emit() self.close() @@ -856,11 +861,12 @@ class SettingsDialog(QtWidgets.QDialog): common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether - if not self.onion.is_authenticated(): - common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + if not self.local_only: + if not self.onion.is_authenticated(): + common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') - # Wait 1ms for the event loop to finish, then quit - QtCore.QTimer.singleShot(1, self.qtapp.quit) + # Wait 1ms for the event loop to finish, then quit + QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): common.log('SettingsDialog', '_update_autoupdate_timestamp') -- cgit v1.2.3-54-g00ecf From 9a800d90b2937389004a3007e399b447b56ffb8a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 7 Mar 2018 10:12:10 -0800 Subject: Started designing HTML/CSS for receive mode --- share/static/css/style.css | 31 ++++++++++++++++++++++++++++++- share/static/img/logo_large.png | Bin 0 -> 9663 bytes share/templates/receive.html | 12 ++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 share/static/img/logo_large.png diff --git a/share/static/css/style.css b/share/static/css/style.css index dc20f24d..c3304f39 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -45,10 +45,11 @@ header .right ul li { font-size: 1rem; } -header .button { +.button { color: #ffffff; background-color: #4e064f; padding: 10px; + border: 0; border-radius: 5px; text-decoration: none; margin-left: 1rem; @@ -86,3 +87,31 @@ table.file-list td img { table.file-list td:last-child { width: 100%; } + +.upload-wrapper { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; +} + +.upload { + text-align: center; +} + +.upload img { + width: 120px; + height: 120px; +} + +.upload .upload-header { + font-size: 30px; + font-weight: normal; + color: #666666; + margin: 0 0 10px 0; +} + +.upload .upload-description { + color: #666666; + margin: 0 0 20px 0; +} diff --git a/share/static/img/logo_large.png b/share/static/img/logo_large.png new file mode 100644 index 00000000..ee8f26ac Binary files /dev/null and b/share/static/img/logo_large.png differ diff --git a/share/templates/receive.html b/share/templates/receive.html index 42fe35d2..6ad3aebc 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -12,5 +12,17 @@

    OnionShare

    +
    +
    +

    +

    Send Files

    +

    Select the files you want to send, then click "Send Files"...

    +
    +

    +

    +
    +
    +
    + -- cgit v1.2.3-54-g00ecf From 2e31db85438421ac47dda7d5b97000249d721539 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 8 Mar 2018 05:43:45 -0800 Subject: Fix settings test because I moved the default downloads dir --- test/test_onionshare_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index acf5cc6a..88997749 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -68,7 +68,7 @@ class TestSettings: 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/Downloads') + 'downloads_dir': os.path.expanduser('~/OnionShare') } def test_fill_in_defaults(self, settings_obj): -- cgit v1.2.3-54-g00ecf From 2e6538b7f8ab9df013e0e1cb438a7c068e94d7bc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 8 Mar 2018 05:45:07 -0800 Subject: Move ZipWriter from common into web, because that's the only place it's used --- onionshare/common.py | 49 ----------------------- onionshare/web.py | 51 +++++++++++++++++++++++- test/conftest.py | 6 +-- test/test_onionshare_common.py | 58 --------------------------- test/test_onionshare_web.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 111 deletions(-) create mode 100644 test/test_onionshare_web.py diff --git a/onionshare/common.py b/onionshare/common.py index 0d00c7b1..218d5e6c 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -28,7 +28,6 @@ import sys import tempfile import threading import time -import zipfile debug = False @@ -226,54 +225,6 @@ def dir_size(start_path): return total_size -class ZipWriter(object): - """ - ZipWriter accepts files and directories and compresses them into a zip file - with. If a zip_filename is not passed in, it will use the default onionshare - filename. - """ - def __init__(self, zip_filename=None, processed_size_callback=None): - if zip_filename: - self.zip_filename = zip_filename - else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), random_string(4, 6)) - - self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) - self.processed_size_callback = processed_size_callback - if self.processed_size_callback is None: - self.processed_size_callback = lambda _: None - self._size = 0 - self.processed_size_callback(self._size) - - def add_file(self, filename): - """ - Add a file to the zip archive. - """ - self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(filename) - self.processed_size_callback(self._size) - - def add_dir(self, filename): - """ - Add a directory, and all of its children, to the zip archive. - """ - dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' - for dirpath, dirnames, filenames in os.walk(filename): - for f in filenames: - full_filename = os.path.join(dirpath, f) - if not os.path.islink(full_filename): - arc_filename = full_filename[len(dir_to_strip):] - self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(full_filename) - self.processed_size_callback(self._size) - - def close(self): - """ - Close the zip archive. - """ - self.z.close() - - class close_after_seconds(threading.Thread): """ Background thread sleeps t hours and returns. diff --git a/onionshare/web.py b/onionshare/web.py index 68684651..c797a9e4 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -26,6 +26,7 @@ import queue import socket import sys import tempfile +import zipfile from distutils.version import LooseVersion as Version from urllib.request import urlopen @@ -324,7 +325,7 @@ class Web(object): self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) # zip up the files and folders - z = common.ZipWriter(processed_size_callback=processed_size_callback) + z = ZipWriter(processed_size_callback=processed_size_callback) for info in self.file_info['files']: z.add_file(info['filename']) for info in self.file_info['dirs']: @@ -415,3 +416,51 @@ class Web(object): urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read() except: pass + + +class ZipWriter(object): + """ + ZipWriter accepts files and directories and compresses them into a zip file + with. If a zip_filename is not passed in, it will use the default onionshare + filename. + """ + def __init__(self, zip_filename=None, processed_size_callback=None): + if zip_filename: + self.zip_filename = zip_filename + else: + self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), common.random_string(4, 6)) + + self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) + self.processed_size_callback = processed_size_callback + if self.processed_size_callback is None: + self.processed_size_callback = lambda _: None + self._size = 0 + self.processed_size_callback(self._size) + + def add_file(self, filename): + """ + Add a file to the zip archive. + """ + self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(filename) + self.processed_size_callback(self._size) + + def add_dir(self, filename): + """ + Add a directory, and all of its children, to the zip archive. + """ + dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' + for dirpath, dirnames, filenames in os.walk(filename): + for f in filenames: + full_filename = os.path.join(dirpath, f) + if not os.path.islink(full_filename): + arc_filename = full_filename[len(dir_to_strip):] + self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(full_filename) + self.processed_size_callback(self._size) + + def close(self): + """ + Close the zip archive. + """ + self.z.close() diff --git a/test/conftest.py b/test/conftest.py index 8f10162b..88a7c054 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -8,7 +8,7 @@ import tempfile import pytest -from onionshare import common +from onionshare import common, web @pytest.fixture def temp_dir_1024(): @@ -64,7 +64,7 @@ def temp_file_1024_delete(): # pytest > 2.9 only needs @pytest.fixture @pytest.yield_fixture(scope='session') def custom_zw(): - zw = common.ZipWriter( + zw = web.ZipWriter( zip_filename=common.random_string(4, 6), processed_size_callback=lambda _: 'custom_callback' ) @@ -76,7 +76,7 @@ def custom_zw(): # pytest > 2.9 only needs @pytest.fixture @pytest.yield_fixture(scope='session') def default_zw(): - zw = common.ZipWriter() + zw = web.ZipWriter() yield zw zw.close() tmp_dir = os.path.dirname(zw.zip_filename) diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index cb864313..66e4a808 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -31,12 +31,10 @@ import pytest from onionshare import common -DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') LOG_MSG_REGEX = re.compile(r""" ^\[Jun\ 06\ 2013\ 11:05:00\] \ TestModule\.\.dummy_func \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE) -RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') @@ -296,59 +294,3 @@ class TestSetDebug: def test_debug_false(self, set_debug_true): common.set_debug(False) assert common.debug is False - - -class TestZipWriterDefault: - @pytest.mark.parametrize('test_input', ( - 'onionshare_{}.zip'.format(''.join( - random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6) - )) for _ in range(50) - )) - def test_default_zw_filename_regex(self, test_input): - assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input)) - - def test_zw_filename(self, default_zw): - zw_filename = os.path.basename(default_zw.zip_filename) - assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename)) - - def test_zipfile_filename_matches_zipwriter_filename(self, default_zw): - assert default_zw.z.filename == default_zw.zip_filename - - def test_zipfile_allow_zip64(self, default_zw): - assert default_zw.z._allowZip64 is True - - def test_zipfile_mode(self, default_zw): - assert default_zw.z.mode == 'w' - - def test_callback(self, default_zw): - assert default_zw.processed_size_callback(None) is None - - def test_add_file(self, default_zw, temp_file_1024_delete): - default_zw.add_file(temp_file_1024_delete) - zipfile_info = default_zw.z.getinfo( - os.path.basename(temp_file_1024_delete)) - - assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED - assert zipfile_info.file_size == 1024 - - def test_add_directory(self, temp_dir_1024_delete, default_zw): - previous_size = default_zw._size # size before adding directory - default_zw.add_dir(temp_dir_1024_delete) - assert default_zw._size == previous_size + 1024 - - -class TestZipWriterCustom: - @pytest.mark.parametrize('test_input', ( - common.random_string( - random.randint(2, 50), - random.choice((None, random.randint(2, 50))) - ) for _ in range(50) - )) - def test_random_string_regex(self, test_input): - assert bool(RANDOM_STR_REGEX.match(test_input)) - - def test_custom_filename(self, custom_zw): - assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename)) - - def test_custom_callback(self, custom_zw): - assert custom_zw.processed_size_callback(None) == 'custom_callback' diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py new file mode 100644 index 00000000..55ff1f85 --- /dev/null +++ b/test/test_onionshare_web.py @@ -0,0 +1,90 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2017 Micah Lee + +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 . +""" + +import contextlib +import inspect +import io +import os +import random +import re +import socket +import sys +import zipfile + +import pytest + +from onionshare import common + +DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') +RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') + +class TestZipWriterDefault: + @pytest.mark.parametrize('test_input', ( + 'onionshare_{}.zip'.format(''.join( + random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6) + )) for _ in range(50) + )) + def test_default_zw_filename_regex(self, test_input): + assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input)) + + def test_zw_filename(self, default_zw): + zw_filename = os.path.basename(default_zw.zip_filename) + assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename)) + + def test_zipfile_filename_matches_zipwriter_filename(self, default_zw): + assert default_zw.z.filename == default_zw.zip_filename + + def test_zipfile_allow_zip64(self, default_zw): + assert default_zw.z._allowZip64 is True + + def test_zipfile_mode(self, default_zw): + assert default_zw.z.mode == 'w' + + def test_callback(self, default_zw): + assert default_zw.processed_size_callback(None) is None + + def test_add_file(self, default_zw, temp_file_1024_delete): + default_zw.add_file(temp_file_1024_delete) + zipfile_info = default_zw.z.getinfo( + os.path.basename(temp_file_1024_delete)) + + assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED + assert zipfile_info.file_size == 1024 + + def test_add_directory(self, temp_dir_1024_delete, default_zw): + previous_size = default_zw._size # size before adding directory + default_zw.add_dir(temp_dir_1024_delete) + assert default_zw._size == previous_size + 1024 + + +class TestZipWriterCustom: + @pytest.mark.parametrize('test_input', ( + common.random_string( + random.randint(2, 50), + random.choice((None, random.randint(2, 50))) + ) for _ in range(50) + )) + def test_random_string_regex(self, test_input): + assert bool(RANDOM_STR_REGEX.match(test_input)) + + def test_custom_filename(self, custom_zw): + assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename)) + + def test_custom_callback(self, custom_zw): + assert custom_zw.processed_size_callback(None) == 'custom_callback' -- cgit v1.2.3-54-g00ecf From 49e352d1313dcd9c8778eb0ed980d3b232f8e8ac Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 8 Mar 2018 05:50:23 -0800 Subject: Rename close_after_seconds class to ShutdownTimer --- onionshare/common.py | 2 +- onionshare/onionshare.py | 3 ++- onionshare_gui/onionshare_gui.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 218d5e6c..d51fcbaf 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -225,7 +225,7 @@ def dir_size(start_path): return total_size -class close_after_seconds(threading.Thread): +class ShutdownTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 85bfaf22..02da4b62 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -21,6 +21,7 @@ along with this program. If not, see . import os, shutil from . import common, strings +from .common import ShutdownTimer class OnionShare(object): """ @@ -74,7 +75,7 @@ class OnionShare(object): return if self.shutdown_timeout > 0: - self.shutdown_timer = common.close_after_seconds(self.shutdown_timeout) + self.shutdown_timer = ShutdownTimer(self.shutdown_timeout) self.onion_host = self.onion.start_onion_service(self.port) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 947499ed..3e7addce 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -24,6 +24,7 @@ import queue from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common +from onionshare.common import ShutdownTimer from onionshare.settings import Settings from onionshare.onion import * @@ -468,7 +469,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: - self.app.shutdown_timer = common.close_after_seconds(self.timeout) + self.app.shutdown_timer = ShutdownTimer(self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: -- cgit v1.2.3-54-g00ecf From 50409167d484a6431efa646fd40d51d02e9e88b2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 8 Mar 2018 10:18:31 -0800 Subject: Completely refactor common to make a Common class, and pass that class down into all parts of the program --- onionshare/__init__.py | 20 +- onionshare/common.py | 375 +++++++++++++++----------------- onionshare/onion.py | 62 +++--- onionshare/onionshare.py | 16 +- onionshare/settings.py | 18 +- onionshare/web.py | 33 +-- onionshare_gui/__init__.py | 29 +-- onionshare_gui/alert.py | 11 +- onionshare_gui/downloads.py | 21 +- onionshare_gui/file_selection.py | 42 ++-- onionshare_gui/onionshare_gui.py | 111 +++++----- onionshare_gui/server_status.py | 13 +- onionshare_gui/settings_dialog.py | 87 ++++---- onionshare_gui/tor_connection_dialog.py | 36 +-- onionshare_gui/update_checker.py | 46 ++-- test/test_onionshare_common.py | 24 +- test/test_onionshare_settings.py | 2 +- 17 files changed, 480 insertions(+), 466 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index ec008f5d..1e07f11c 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -20,7 +20,8 @@ along with this program. If not, see . import os, sys, time, argparse, threading -from . import strings, common +from . import strings +from .common import Common from .web import Web from .onion import * from .onionshare import OnionShare @@ -31,11 +32,13 @@ def main(cwd=None): The main() function implements all of the logic that the command-line version of onionshare uses. """ + common = Common() + strings.load_strings(common) - print(strings._('version_string').format(common.get_version())) + print(strings._('version_string').format(common.version)) # OnionShare CLI in OSX needs to change current working directory (#132) - if common.get_platform() == 'Darwin': + if common.platform == 'Darwin': if cwd: os.chdir(cwd) @@ -69,8 +72,7 @@ def main(cwd=None): sys.exit() # Debug mode? - if debug: - common.set_debug(debug) + common.debug = debug # Validate filenames if not receive: @@ -86,7 +88,7 @@ def main(cwd=None): sys.exit() # Load settings - settings = Settings(config) + settings = Settings(common, config) settings.load() # In receive mode, validate downloads dir @@ -105,10 +107,10 @@ def main(cwd=None): sys.exit() # Create the Web object - web = Web(debug, stay_open, False, receive) + web = Web(common, stay_open, False, receive) # Start the Onion object - onion = Onion() + onion = Onion(common) try: onion.connect(settings=False, config=config) except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: @@ -119,7 +121,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) app.start_onion_service() except KeyboardInterrupt: diff --git a/onionshare/common.py b/onionshare/common.py index d51fcbaf..36848738 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -29,212 +29,197 @@ import tempfile import threading import time -debug = False - - -def log(module, func, msg=None): - """ - If debug mode is on, log error messages to stdout - """ - global debug - if debug: - timestamp = time.strftime("%b %d %Y %X") - - final_msg = "[{}] {}.{}".format(timestamp, module, func) - if msg: - final_msg = '{}: {}'.format(final_msg, msg) - print(final_msg) - - -def set_debug(new_debug): - global debug - debug = new_debug - - -def get_platform(): - """ - Returns the platform OnionShare is running on. - """ - plat = platform.system() - if plat.endswith('BSD'): - plat = 'BSD' - return plat - - -def get_resource_path(filename): - """ - Returns the absolute path of a resource, regardless of whether OnionShare is installed - systemwide, and whether regardless of platform - """ - p = get_platform() - - # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes - if p == 'Windows': - filename = filename.replace('/', '\\') - - if getattr(sys, 'onionshare_dev_mode', False): - # Look for resources directory relative to python file - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') - if not os.path.exists(prefix): - # While running tests during stdeb bdist_deb, look 3 directories up for the share folder - prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') - - elif p == 'BSD' or p == 'Linux': - # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode - prefix = os.path.join(sys.prefix, 'share/onionshare') - - elif getattr(sys, 'frozen', False): - # Check if app is "frozen" - # https://pythonhosted.org/PyInstaller/#run-time-information - if p == 'Darwin': - prefix = os.path.join(sys._MEIPASS, 'share') - elif p == 'Windows': - prefix = os.path.join(os.path.dirname(sys.executable), 'share') - - return os.path.join(prefix, filename) - - -def get_tor_paths(): - p = get_platform() - if p == 'Linux': - tor_path = '/usr/bin/tor' - tor_geo_ip_file_path = '/usr/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' - obfs4proxy_file_path = '/usr/bin/obfs4proxy' - elif p == 'Windows': - base_path = os.path.join(os.path.dirname(os.path.dirname(get_resource_path(''))), 'tor') - tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') - obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') - tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') - tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - elif p == 'Darwin': - base_path = os.path.dirname(os.path.dirname(os.path.dirname(get_resource_path('')))) - tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') - tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') - tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') - obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy') - elif p == 'BSD': - tor_path = '/usr/local/bin/tor' - tor_geo_ip_file_path = '/usr/local/share/tor/geoip' - tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' - obfs4proxy_file_path = '/usr/local/bin/obfs4proxy' - - return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) - - -def get_version(): - """ - Returns the version of OnionShare that is running. - """ - with open(get_resource_path('version.txt')) as f: - version = f.read().strip() - return version - - -def random_string(num_bytes, output_len=None): - """ - Returns a random string with a specified number of bytes. - """ - b = os.urandom(num_bytes) - h = hashlib.sha256(b).digest()[:16] - s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') - if not output_len: - return s - return s[:output_len] - - -def build_slug(): - """ - Returns a random string made from two words from the wordlist, such as "deter-trig". - """ - with open(get_resource_path('wordlist.txt')) as f: - wordlist = f.read().split() - - r = random.SystemRandom() - return '-'.join(r.choice(wordlist) for _ in range(2)) - - -def human_readable_filesize(b): - """ - Returns filesize in a human readable format. - """ - thresh = 1024.0 - if b < thresh: - return '{:.1f} B'.format(b) - units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') - u = 0 - b /= thresh - while b >= thresh: +class Common(object): + """ + The Common object is shared amongst all parts of OnionShare. + """ + def __init__(self, debug=False): + self.debug = debug + + # The platform OnionShare is running on + self.platform = platform.system() + if self.platform.endswith('BSD'): + self.platform = 'BSD' + + # The current version of OnionShare + with open(self.get_resource_path('version.txt')) as f: + self.version = f.read().strip() + + def log(self, module, func, msg=None): + """ + If debug mode is on, log error messages to stdout + """ + if self.debug: + timestamp = time.strftime("%b %d %Y %X") + + final_msg = "[{}] {}.{}".format(timestamp, module, func) + if msg: + final_msg = '{}: {}'.format(final_msg, msg) + print(final_msg) + + def get_resource_path(self, filename): + """ + Returns the absolute path of a resource, regardless of whether OnionShare is installed + systemwide, and whether regardless of platform + """ + # On Windows, and in Windows dev mode, switch slashes in incoming filename to backslackes + if self.platform == 'Windows': + filename = filename.replace('/', '\\') + + if getattr(sys, 'onionshare_dev_mode', False): + # Look for resources directory relative to python file + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))), 'share') + if not os.path.exists(prefix): + # While running tests during stdeb bdist_deb, look 3 directories up for the share folder + prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share') + + elif self.platform == 'BSD' or self.platform == 'Linux': + # Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode + prefix = os.path.join(sys.prefix, 'share/onionshare') + + elif getattr(sys, 'frozen', False): + # Check if app is "frozen" + # https://pythonhosted.org/PyInstaller/#run-time-information + if self.platform == 'Darwin': + prefix = os.path.join(sys._MEIPASS, 'share') + elif self.platform == 'Windows': + prefix = os.path.join(os.path.dirname(sys.executable), 'share') + + return os.path.join(prefix, filename) + + def get_tor_paths(self): + if self.platform == 'Linux': + tor_path = '/usr/bin/tor' + tor_geo_ip_file_path = '/usr/share/tor/geoip' + tor_geo_ipv6_file_path = '/usr/share/tor/geoip6' + obfs4proxy_file_path = '/usr/bin/obfs4proxy' + elif self.platform == 'Windows': + base_path = os.path.join(os.path.dirname(os.path.dirname(self.get_resource_path(''))), 'tor') + tor_path = os.path.join(os.path.join(base_path, 'Tor'), 'tor.exe') + obfs4proxy_file_path = os.path.join(os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') + tor_geo_ip_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip') + tor_geo_ipv6_file_path = os.path.join(os.path.join(os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') + elif self.platform == 'Darwin': + base_path = os.path.dirname(os.path.dirname(os.path.dirname(self.get_resource_path('')))) + tor_path = os.path.join(base_path, 'Resources', 'Tor', 'tor') + tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip') + tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6') + obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy') + elif self.platform == 'BSD': + tor_path = '/usr/local/bin/tor' + tor_geo_ip_file_path = '/usr/local/share/tor/geoip' + tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6' + obfs4proxy_file_path = '/usr/local/bin/obfs4proxy' + + return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) + + def build_slug(self): + """ + Returns a random string made from two words from the wordlist, such as "deter-trig". + """ + with open(self.get_resource_path('wordlist.txt')) as f: + wordlist = f.read().split() + + r = random.SystemRandom() + return '-'.join(r.choice(wordlist) for _ in range(2)) + + @staticmethod + def random_string(num_bytes, output_len=None): + """ + Returns a random string with a specified number of bytes. + """ + b = os.urandom(num_bytes) + h = hashlib.sha256(b).digest()[:16] + s = base64.b32encode(h).lower().replace(b'=', b'').decode('utf-8') + if not output_len: + return s + return s[:output_len] + + @staticmethod + def human_readable_filesize(b): + """ + Returns filesize in a human readable format. + """ + thresh = 1024.0 + if b < thresh: + return '{:.1f} B'.format(b) + units = ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') + u = 0 b /= thresh - u += 1 - return '{:.1f} {}'.format(b, units[u]) - - -def format_seconds(seconds): - """Return a human-readable string of the format 1d2h3m4s""" - days, seconds = divmod(seconds, 86400) - hours, seconds = divmod(seconds, 3600) - minutes, seconds = divmod(seconds, 60) - - human_readable = [] - if days: - human_readable.append("{:.0f}d".format(days)) - if hours: - human_readable.append("{:.0f}h".format(hours)) - if minutes: - human_readable.append("{:.0f}m".format(minutes)) - if seconds or not human_readable: - human_readable.append("{:.0f}s".format(seconds)) - return ''.join(human_readable) - - -def estimated_time_remaining(bytes_downloaded, total_bytes, started): - now = time.time() - time_elapsed = now - started # in seconds - download_rate = bytes_downloaded / time_elapsed - remaining_bytes = total_bytes - bytes_downloaded - eta = remaining_bytes / download_rate - return format_seconds(eta) - - -def get_available_port(min_port, max_port): - """ - Find a random available port within the given range. - """ - with socket.socket() as tmpsock: - while True: - try: - tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port))) - break - except OSError as e: - raise OSError(e) - _, port = tmpsock.getsockname() - return port - - -def dir_size(start_path): - """ - Calculates the total size, in bytes, of all of the files in a directory. - """ - total_size = 0 - for dirpath, dirnames, filenames in os.walk(start_path): - for f in filenames: - fp = os.path.join(dirpath, f) - if not os.path.islink(fp): - total_size += os.path.getsize(fp) - return total_size + while b >= thresh: + b /= thresh + u += 1 + return '{:.1f} {}'.format(b, units[u]) + + @staticmethod + def format_seconds(seconds): + """Return a human-readable string of the format 1d2h3m4s""" + days, seconds = divmod(seconds, 86400) + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + + human_readable = [] + if days: + human_readable.append("{:.0f}d".format(days)) + if hours: + human_readable.append("{:.0f}h".format(hours)) + if minutes: + human_readable.append("{:.0f}m".format(minutes)) + if seconds or not human_readable: + human_readable.append("{:.0f}s".format(seconds)) + return ''.join(human_readable) + + @staticmethod + def estimated_time_remaining(bytes_downloaded, total_bytes, started): + now = time.time() + time_elapsed = now - started # in seconds + download_rate = bytes_downloaded / time_elapsed + remaining_bytes = total_bytes - bytes_downloaded + eta = remaining_bytes / download_rate + return format_seconds(eta) + + @staticmethod + def get_available_port(min_port, max_port): + """ + Find a random available port within the given range. + """ + with socket.socket() as tmpsock: + while True: + try: + tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port))) + break + except OSError as e: + raise OSError(e) + _, port = tmpsock.getsockname() + return port + + @staticmethod + def dir_size(start_path): + """ + Calculates the total size, in bytes, of all of the files in a directory. + """ + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + if not os.path.islink(fp): + total_size += os.path.getsize(fp) + return total_size class ShutdownTimer(threading.Thread): """ Background thread sleeps t hours and returns. """ - def __init__(self, time): + def __init__(self, common, time): threading.Thread.__init__(self) + + self.common = common + self.setDaemon(True) self.time = time def run(self): - log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) + self.common.log('Shutdown Timer', 'Server will shut down after {} seconds'.format(self.time)) time.sleep(self.time) return 1 diff --git a/onionshare/onion.py b/onionshare/onion.py index 068648ba..4b3b0971 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -125,22 +125,22 @@ class Onion(object): call this function and pass in a status string while connecting to tor. This is necessary for status updates to reach the GUI. """ - def __init__(self): - common.log('Onion', '__init__') + def __init__(self, common): + self.common = common + + self.common.log('Onion', '__init__') self.stealth = False self.service_id = None - self.system = common.get_platform() - # Is bundled tor supported? - if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): + if (self.common.platform == 'Windows' or self.common.platform == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False): self.bundle_tor_supported = False else: self.bundle_tor_supported = True # Set the path of the tor binary, for bundled tor - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = 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() # The tor process self.tor_proc = None @@ -149,13 +149,13 @@ class Onion(object): self.connected_to_tor = False def connect(self, settings=False, config=False, tor_status_update_func=None): - common.log('Onion', 'connect') + self.common.log('Onion', 'connect') # Either use settings that are passed in, or load them from disk if settings: self.settings = settings else: - self.settings = Settings(config) + self.settings = Settings(self.common, config) self.settings.load() # The Tor controller @@ -168,29 +168,29 @@ class Onion(object): # Create a torrc for this session self.tor_data_directory = tempfile.TemporaryDirectory() - if self.system == 'Windows': + if self.common.platform == 'Windows': # Windows needs to use network ports, doesn't support unix sockets - torrc_template = open(common.get_resource_path('torrc_template-windows')).read() + torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read() try: - self.tor_control_port = common.get_available_port(1000, 65535) + self.tor_control_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_control_socket = None self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') try: - self.tor_socks_port = common.get_available_port(1000, 65535) + self.tor_socks_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') else: # Linux, Mac and BSD can use unix sockets - with open(common.get_resource_path('torrc_template')) as f: + with open(self.common.get_resource_path('torrc_template')) as f: torrc_template = f.read() self.tor_control_port = None self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') try: - self.tor_socks_port = common.get_available_port(1000, 65535) + self.tor_socks_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') @@ -208,17 +208,17 @@ class Onion(object): # Bridge support if self.settings.get('tor_bridges_use_obfs4'): f.write('ClientTransportPlugin obfs4 exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-obfs4')) as o: + with open(self.common.get_resource_path('torrc_template-obfs4')) as o: for line in o: f.write(line) elif self.settings.get('tor_bridges_use_meek_lite_amazon'): f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-meek_lite_amazon')) as o: + with open(self.common.get_resource_path('torrc_template-meek_lite_amazon')) as o: for line in o: f.write(line) elif self.settings.get('tor_bridges_use_meek_lite_azure'): f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(common.get_resource_path('torrc_template-meek_lite_azure')) as o: + with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o: for line in o: f.write(line) @@ -232,7 +232,7 @@ class Onion(object): # Execute a tor subprocess start_ts = time.time() - if self.system == 'Windows': + if self.common.platform == 'Windows': # In Windows, hide console window when opening tor.exe subprocess startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW @@ -245,7 +245,7 @@ class Onion(object): # Connect to the controller try: - if self.system == 'Windows': + if self.common.platform == 'Windows': self.c = Controller.from_port(port=self.tor_control_port) self.c.authenticate() else: @@ -270,7 +270,7 @@ class Onion(object): if callable(tor_status_update_func): if not tor_status_update_func(progress, summary): # If the dialog was canceled, stop connecting to Tor - common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor') + self.common.log('Onion', 'connect', 'tor_status_update_func returned false, canceling connecting to Tor') print() return False @@ -322,7 +322,7 @@ class Onion(object): socket_file_path = '' if not found_tor: try: - if self.system == 'Darwin': + if self.common.platform == 'Darwin': socket_file_path = os.path.expanduser('~/Library/Application Support/TorBrowser-Data/Tor/control.socket') self.c = Controller.from_socket_file(path=socket_file_path) @@ -334,11 +334,11 @@ class Onion(object): # guessing the socket file name next if not found_tor: try: - if self.system == 'Linux' or self.system == 'BSD': + if self.common.platform == 'Linux' or self.common.platform == 'BSD': socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.system == 'Darwin': + elif self.common.platform == 'Darwin': socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid()) - elif self.system == 'Windows': + elif self.common.platform == 'Windows': # Windows doesn't support unix sockets raise TorErrorAutomatic(strings._('settings_error_automatic')) @@ -424,7 +424,7 @@ class Onion(object): Start a onion service on port 80, pointing to the given port, and return the onion hostname. """ - common.log('Onion', 'start_onion_service') + self.common.log('Onion', 'start_onion_service') self.auth_string = None if not self.supports_ephemeral: @@ -447,11 +447,11 @@ class Onion(object): if self.settings.get('private_key'): key_type = "RSA1024" key_content = self.settings.get('private_key') - common.log('Onion', 'Starting a hidden service with a saved private key') + self.common.log('Onion', 'Starting a hidden service with a saved private key') else: key_type = "NEW" key_content = "RSA1024" - common.log('Onion', 'Starting a hidden service with a new private key') + self.common.log('Onion', 'Starting a hidden service with a new private key') try: if basic_auth != None: @@ -498,17 +498,17 @@ class Onion(object): """ Stop onion services that were created earlier. If there's a tor subprocess running, kill it. """ - common.log('Onion', 'cleanup') + self.common.log('Onion', 'cleanup') # Cleanup the ephemeral onion services, if we have any try: onions = self.c.list_ephemeral_hidden_services() for onion in onions: try: - common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion)) + self.common.log('Onion', 'cleanup', 'trying to remove onion {}'.format(onion)) self.c.remove_ephemeral_hidden_service(onion) except: - common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion)) + self.common.log('Onion', 'cleanup', 'could not remove onion {}.. moving on anyway'.format(onion)) pass except: pass @@ -545,7 +545,7 @@ class Onion(object): """ Returns a (address, port) tuple for the Tor SOCKS port """ - common.log('Onion', 'get_tor_socks_port') + self.common.log('Onion', 'get_tor_socks_port') if self.settings.get('connection_type') == 'bundled': return ('127.0.0.1', self.tor_socks_port) diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 02da4b62..10d73751 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -28,8 +28,10 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, onion, local_only=False, stay_open=False, shutdown_timeout=0): - common.log('OnionShare', '__init__') + def __init__(self, common, onion, local_only=False, stay_open=False, shutdown_timeout=0): + self.common = common + + self.common.log('OnionShare', '__init__') # The Onion object self.onion = onion @@ -53,7 +55,7 @@ class OnionShare(object): self.shutdown_timer = None def set_stealth(self, stealth): - common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) + self.common.log('OnionShare', 'set_stealth', 'stealth={}'.format(stealth)) self.stealth = stealth self.onion.stealth = stealth @@ -62,11 +64,11 @@ class OnionShare(object): """ Start the onionshare onion service. """ - common.log('OnionShare', 'start_onion_service') + self.common.log('OnionShare', 'start_onion_service') # Choose a random port try: - self.port = common.get_available_port(17600, 17650) + self.port = self.common.get_available_port(17600, 17650) except: raise OSError(strings._('no_available_port')) @@ -75,7 +77,7 @@ class OnionShare(object): return if self.shutdown_timeout > 0: - self.shutdown_timer = ShutdownTimer(self.shutdown_timeout) + self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout) self.onion_host = self.onion.start_onion_service(self.port) @@ -86,7 +88,7 @@ class OnionShare(object): """ Shut everything down and clean up temporary files, etc. """ - common.log('OnionShare', 'cleanup') + self.common.log('OnionShare', 'cleanup') # cleanup files for filename in self.cleanup_filenames: diff --git a/onionshare/settings.py b/onionshare/settings.py index 9b61beaf..1c7638c0 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -22,7 +22,7 @@ import json import os import platform -from . import strings, common +from . import strings class Settings(object): @@ -32,8 +32,10 @@ class Settings(object): which is to attempt to connect automatically using default Tor Browser settings. """ - def __init__(self, config=False): - common.log('Settings', '__init__') + def __init__(self, common, config=False): + self.common = common + + self.common.log('Settings', '__init__') # Default config self.filename = self.build_filename() @@ -43,11 +45,11 @@ class Settings(object): if os.path.isfile(config): self.filename = config else: - 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') # These are the default settings. They will get overwritten when loading from disk self.default_settings = { - 'version': common.get_version(), + 'version': self.common.version, 'connection_type': 'bundled', 'control_port_address': '127.0.0.1', 'control_port_port': 9051, @@ -110,12 +112,12 @@ class Settings(object): """ Load the settings from file. """ - common.log('Settings', 'load') + self.common.log('Settings', 'load') # If the settings file exists, load it if os.path.exists(self.filename): try: - common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) + self.common.log('Settings', 'load', 'Trying to load {}'.format(self.filename)) with open(self.filename, 'r') as f: self._settings = json.load(f) self.fill_in_defaults() @@ -126,7 +128,7 @@ class Settings(object): """ Save settings to file. """ - common.log('Settings', 'save') + self.common.log('Settings', 'save') try: os.makedirs(os.path.dirname(self.filename)) diff --git a/onionshare/web.py b/onionshare/web.py index c797a9e4..b6739bcb 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -41,14 +41,16 @@ class Web(object): """ The Web object is the OnionShare web server, powered by flask """ - def __init__(self, debug, stay_open, gui_mode, receive_mode=False): + def __init__(self, common, stay_open, gui_mode, receive_mode=False): + self.common = common + # The flask app self.app = Flask(__name__, static_folder=common.get_resource_path('static'), template_folder=common.get_resource_path('templates')) # Debug mode? - if debug: + if self.common.debug: self.debug_mode() # Stay open after the first download? @@ -107,7 +109,7 @@ class Web(object): self.client_cancel = False # shutting down the server only works within the context of flask, so the easiest way to do it is over http - self.shutdown_slug = common.random_string(16) + self.shutdown_slug = self.common.random_string(16) # Define the ewb app routes self.common_routes() @@ -143,7 +145,7 @@ class Web(object): file_info=self.file_info, filename=os.path.basename(self.zip_filename), filesize=self.zip_filesize, - filesize_human=common.human_readable_filesize(self.zip_filesize))) + filesize_human=self.common.human_readable_filesize(self.zip_filesize))) return self.add_security_headers(r) @self.app.route("//download") @@ -206,10 +208,9 @@ class Web(object): percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - plat = common.get_platform() - if not self.gui_mode or plat == 'Linux' or plat == 'BSD': + if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent)) + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) sys.stdout.flush() self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) @@ -224,7 +225,7 @@ class Web(object): fp.close() - if common.get_platform() != 'Darwin': + if self.common.platform != 'Darwin': sys.stdout.write("\n") # Download is finished @@ -315,17 +316,17 @@ class Web(object): } if os.path.isfile(filename): info['size'] = os.path.getsize(filename) - info['size_human'] = common.human_readable_filesize(info['size']) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['files'].append(info) if os.path.isdir(filename): - info['size'] = common.dir_size(filename) - info['size_human'] = common.human_readable_filesize(info['size']) + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['dirs'].append(info) self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) # zip up the files and folders - z = ZipWriter(processed_size_callback=processed_size_callback) + z = ZipWriter(self.common, processed_size_callback=processed_size_callback) for info in self.file_info['files']: z.add_file(info['filename']) for info in self.file_info['dirs']: @@ -353,7 +354,7 @@ class Web(object): if persistent_slug: self.slug = persistent_slug else: - self.slug = common.build_slug() + self.slug = self.common.build_slug() def debug_mode(self): """ @@ -424,11 +425,13 @@ class ZipWriter(object): with. If a zip_filename is not passed in, it will use the default onionshare filename. """ - def __init__(self, zip_filename=None, processed_size_callback=None): + def __init__(self, common, zip_filename=None, processed_size_callback=None): + self.common = common + if zip_filename: self.zip_filename = zip_filename else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), common.random_string(4, 6)) + self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) self.processed_size_callback = processed_size_callback diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 205f1c82..04fe1e62 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -22,7 +22,8 @@ import os, sys, platform, argparse from .alert import Alert from PyQt5 import QtCore, QtWidgets -from onionshare import strings, common +from onionshare import strings +from onionshare.common import Common from onionshare.web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare @@ -35,9 +36,8 @@ class Application(QtWidgets.QApplication): This is Qt's QApplication class. It has been overridden to support threads and the quick keyboard shortcut. """ - def __init__(self): - system = common.get_platform() - if system == 'Linux' or system == 'BSD': + def __init__(self, common): + if common.platform == 'Linux' or common.platform == 'BSD': self.setAttribute(QtCore.Qt.AA_X11InitThreads, True) QtWidgets.QApplication.__init__(self, sys.argv) self.installEventFilter(self) @@ -54,12 +54,14 @@ def main(): """ The main() function implements all of the logic that the GUI version of onionshare uses. """ + common = Common() + strings.load_strings(common) - print(strings._('version_string').format(common.get_version())) + print(strings._('version_string').format(common.version)) # Start the Qt app global qtapp - qtapp = Application() + qtapp = Application(common) # Parse arguments parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) @@ -84,34 +86,33 @@ def main(): debug = bool(args.debug) # Debug mode? - if debug: - common.set_debug(debug) + common.debug = debug # Validation if filenames: valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): - Alert(strings._("not_a_file", True).format(filename)) + Alert(self.common, strings._("not_a_file", True).format(filename)) valid = False if not os.access(filename, os.R_OK): - Alert(strings._("not_a_readable_file", True).format(filename)) + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) valid = False if not valid: sys.exit() # Create the Web object - web = Web(debug, stay_open, True) + web = Web(common, stay_open, True) # Start the Onion - onion = Onion() + onion = Onion(common) # Start the OnionShare app - app = OnionShare(onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) # Launch the gui web.stay_open = stay_open - gui = OnionShareGui(web, onion, qtapp, app, filenames, config) + gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/alert.py b/onionshare_gui/alert.py index 814ff786..981225c6 100644 --- a/onionshare_gui/alert.py +++ b/onionshare_gui/alert.py @@ -19,18 +19,19 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import common - class Alert(QtWidgets.QMessageBox): """ An alert box dialog. """ - def __init__(self, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): + def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): super(Alert, self).__init__(None) - common.log('Alert', '__init__') + + self.common = common + + self.common.log('Alert', '__init__') self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setText(message) self.setIcon(icon) self.setStandardButtons(buttons) diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 166f14a4..db82d30a 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -21,11 +21,13 @@ import time from PyQt5 import QtCore, QtWidgets -from onionshare import strings, common +from onionshare import strings class Download(object): - def __init__(self, download_id, total_bytes): + 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 @@ -64,7 +66,7 @@ class Download(object): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: pb_fmt = strings._('gui_download_progress_complete').format( - common.format_seconds(time.time() - self.started)) + self.common.format_seconds(time.time() - self.started)) else: elapsed = time.time() - self.started if elapsed < 10: @@ -72,10 +74,10 @@ class Download(object): # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. pb_fmt = strings._('gui_download_progress_starting').format( - common.human_readable_filesize(downloaded_bytes)) + self.common.human_readable_filesize(downloaded_bytes)) else: pb_fmt = strings._('gui_download_progress_eta').format( - common.human_readable_filesize(downloaded_bytes), + self.common.human_readable_filesize(downloaded_bytes), self.estimated_time_remaining) self.progress_bar.setFormat(pb_fmt) @@ -85,7 +87,7 @@ class Download(object): @property def estimated_time_remaining(self): - return common.estimated_time_remaining(self.downloaded_bytes, + return self.common.estimated_time_remaining(self.downloaded_bytes, self.total_bytes, self.started) @@ -95,8 +97,11 @@ class Downloads(QtWidgets.QWidget): The downloads chunk of the GUI. This lists all of the active download progress bars. """ - def __init__(self): + def __init__(self, common): super(Downloads, self).__init__() + + self.common = common + self.downloads = {} self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout) @@ -108,7 +113,7 @@ class Downloads(QtWidgets.QWidget): self.parent().show() # add it to the list - download = Download(download_id, total_bytes) + download = Download(self.common, download_id, total_bytes) self.downloads[download_id] = download self.layout.insertWidget(-1, download.progress_bar) diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py index 29bcc592..fbc4995b 100644 --- a/onionshare_gui/file_selection.py +++ b/onionshare_gui/file_selection.py @@ -21,21 +21,24 @@ import os from PyQt5 import QtCore, QtWidgets, QtGui from .alert import Alert -from onionshare import strings, common +from onionshare import strings class DropHereLabel(QtWidgets.QLabel): """ When there are no files or folders in the FileList yet, display the 'drop files here' message and graphic. """ - def __init__(self, parent, image=False): + def __init__(self, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) + + self.common = common + self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(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: self.setText(strings._('gui_drag_and_drop', True)) self.setStyleSheet('color: #999999;') @@ -53,9 +56,12 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ - def __init__(self, parent): + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) + + self.common = common + self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) self.setText(strings._('gui_drag_and_drop', True)) @@ -74,16 +80,19 @@ class FileList(QtWidgets.QListWidget): files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() - def __init__(self, parent=None): + def __init__(self, common, parent=None): super(FileList, self).__init__(parent) + + self.common = common + self.setAcceptDrops(True) self.setIconSize(QtCore.QSize(32, 32)) self.setSortingEnabled(True) self.setMinimumHeight(205) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.drop_here_image = DropHereLabel(self, True) - self.drop_here_text = DropHereLabel(self, False) - self.drop_count = DropCountLabel(self) + self.drop_here_image = DropHereLabel(self.common, self, True) + self.drop_here_text = DropHereLabel(self.common, self, False) + self.drop_count = DropCountLabel(self.common, self) self.resizeEvent(None) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) @@ -206,7 +215,7 @@ class FileList(QtWidgets.QListWidget): if filename not in filenames: if not os.access(filename, os.R_OK): - Alert(strings._("not_a_readable_file", True).format(filename)) + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) return fileinfo = QtCore.QFileInfo(filename) @@ -215,10 +224,10 @@ class FileList(QtWidgets.QListWidget): if os.path.isfile(filename): size_bytes = fileinfo.size() - size_readable = common.human_readable_filesize(size_bytes) + size_readable = self.common.human_readable_filesize(size_bytes) else: - size_bytes = common.dir_size(filename) - size_readable = common.human_readable_filesize(size_bytes) + size_bytes = self.common.dir_size(filename) + size_readable = self.common.human_readable_filesize(size_bytes) # Create a new item item = QtWidgets.QListWidgetItem() @@ -245,7 +254,7 @@ class FileList(QtWidgets.QListWidget): item.item_button = QtWidgets.QPushButton() item.item_button.setDefault(False) item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) ) + item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) item.item_button.clicked.connect(delete_item) item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) @@ -277,12 +286,15 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ - def __init__(self): + def __init__(self, common): super(FileSelection, self).__init__() + + self.common = common + self.server_on = False # File list - self.file_list = FileList() + self.file_list = FileList(self.common) self.file_list.itemSelectionChanged.connect(self.update) self.file_list.files_dropped.connect(self.update) self.file_list.files_updated.connect(self.update) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3e7addce..04b8a066 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -24,7 +24,7 @@ import queue from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common -from onionshare.common import ShutdownTimer +from onionshare.common import Common, ShutdownTimer from onionshare.settings import Settings from onionshare.onion import * @@ -47,12 +47,13 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - def __init__(self, web, onion, qtapp, app, filenames, config=False): + def __init__(self, common, web, onion, qtapp, app, filenames, config=False): super(OnionShareGui, self).__init__() - self._initSystemTray() + self.common = common + self.common.log('OnionShareGui', '__init__') - common.log('OnionShareGui', '__init__') + self._initSystemTray() self.web = web self.onion = onion @@ -60,22 +61,22 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app = app self.setWindowTitle('OnionShare') - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) # Load settings self.config = config - self.settings = Settings(self.config) + self.settings = Settings(self.common, self.config) self.settings.load() # File selection - self.file_selection = FileSelection() + self.file_selection = FileSelection(self.common) if filenames: for filename in filenames: self.file_selection.file_list.add_file(filename) # Server status - self.server_status = ServerStatus(self.qtapp, self.app, self.web, self.file_selection, self.settings) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection, self.settings) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) @@ -107,7 +108,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.hide() # Downloads - self.downloads = Downloads() + self.downloads = Downloads(self.common) self.downloads_container = QtWidgets.QScrollArea() self.downloads_container.setWidget(self.downloads) self.downloads_container.setWidgetResizable(True) @@ -147,13 +148,13 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_button.setDefault(False) self.settings_button.setFlat(True) self.settings_button.setFixedWidth(40) - self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) ) + self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(common.get_resource_path('images/server_started.png')) + self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) + self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) + self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel() @@ -221,7 +222,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() - tor_con = TorConnectionDialog(self.qtapp, self.settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.settings, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() @@ -244,7 +245,7 @@ class OnionShareGui(QtWidgets.QMainWindow): for index in range(self.file_selection.file_list.count()): item = self.file_selection.file_list.item(index) total_size_bytes += item.size_bytes - total_size_readable = common.human_readable_filesize(total_size_bytes) + total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) @@ -259,7 +260,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.adjustSize() def update_server_status_indicator(self): - common.log('OnionShareGui', 'update_server_status_indicator') + self.common.log('OnionShareGui', 'update_server_status_indicator') # Set the status image if self.server_status.status == self.server_status.STATUS_STOPPED: @@ -273,8 +274,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_label.setText(strings._('gui_status_indicator_started', True)) def _initSystemTray(self): - system = common.get_platform() - menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) @@ -285,10 +284,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.systemTray = QtWidgets.QSystemTrayIcon(self) # The convention is Mac systray icons are always grayscale - if system == 'Darwin': - self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo_grayscale.png'))) + if self.common.platform == 'Darwin': + self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) else: - self.systemTray.setIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.systemTray.setContextMenu(menu) self.systemTray.show() @@ -297,10 +296,10 @@ class OnionShareGui(QtWidgets.QMainWindow): If the user cancels before Tor finishes connecting, ask if they want to quit, or open settings. """ - common.log('OnionShareGui', '_tor_connection_canceled') + self.common.log('OnionShareGui', '_tor_connection_canceled') def ask(): - a = Alert(strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) + a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) @@ -310,12 +309,12 @@ class OnionShareGui(QtWidgets.QMainWindow): if a.clickedButton() == settings_button: # Open settings - common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') + self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked') self.open_settings() if a.clickedButton() == quit_button: # Quit - common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') + self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) @@ -327,7 +326,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ The TorConnectionDialog wants to open the Settings dialog """ - common.log('OnionShareGui', '_tor_connection_open_settings') + self.common.log('OnionShareGui', '_tor_connection_open_settings') # Wait 1ms for the event loop to finish closing the TorConnectionDialog QtCore.QTimer.singleShot(1, self.open_settings) @@ -336,10 +335,10 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Open the SettingsDialog. """ - common.log('OnionShareGui', 'open_settings') + self.common.log('OnionShareGui', 'open_settings') def reload_settings(): - common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') + self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new @@ -356,7 +355,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if not self.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() - d = SettingsDialog(self.onion, self.qtapp, self.config) + d = SettingsDialog(self.common, self.onion, self.qtapp, self.config) d.settings_saved.connect(reload_settings) d.exec_() @@ -368,7 +367,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - common.log('OnionShareGui', 'start_server') + self.common.log('OnionShareGui', 'start_server') self.set_server_active(True) @@ -405,8 +404,8 @@ class OnionShareGui(QtWidgets.QMainWindow): # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) - common.log('OnionshareGui', 'start_server', 'Starting an onion thread') - self.t = OnionThread(function=start_onion_service, kwargs={'self': self}) + self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() @@ -414,7 +413,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Step 2 in starting the onionshare server. Zipping up files. """ - common.log('OnionShareGui', 'start_server_step2') + self.common.log('OnionShareGui', 'start_server_step2') # add progress bar to the status bar, indicating the crunching of files. self._zip_progress_bar = ZipProgressBar(0) @@ -451,7 +450,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ - common.log('OnionShareGui', 'start_server_step3') + self.common.log('OnionShareGui', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: @@ -469,7 +468,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timeout = now.secsTo(self.server_status.timeout) # Set the shutdown timeout value if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.timeout) + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) self.app.shutdown_timer.start() # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: @@ -480,11 +479,11 @@ class OnionShareGui(QtWidgets.QMainWindow): """ If there's an error when trying to start the onion service """ - common.log('OnionShareGui', 'start_server_error') + self.common.log('OnionShareGui', 'start_server_error') self.set_server_active(False) - Alert(error, QtWidgets.QMessageBox.Warning) + Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) @@ -503,7 +502,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Stop the onionshare server. """ - common.log('OnionShareGui', 'stop_server') + self.common.log('OnionShareGui', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: @@ -527,13 +526,12 @@ class OnionShareGui(QtWidgets.QMainWindow): """ Check for updates in a new thread, if enabled. """ - system = common.get_platform() - if system == 'Windows' or system == 'Darwin': + if self.common.platform == 'Windows' or self.common.platform == 'Darwin': if self.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): - Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) - self.update_thread = UpdateThread(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.start() @@ -544,7 +542,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if os.path.isfile(filename): total_size += os.path.getsize(filename) if os.path.isdir(filename): - total_size += common.dir_size(filename) + total_size += Common.dir_size(filename) return total_size def check_for_requests(self): @@ -594,7 +592,7 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == self.web.REQUEST_RATE_LIMIT: self.stop_server() - Alert(strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) elif event["type"] == self.web.REQUEST_PROGRESS: self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) @@ -655,7 +653,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the URL gets copied to the clipboard, display this in the status bar. """ - common.log('OnionShareGui', 'copy_url') + self.common.log('OnionShareGui', 'copy_url') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) @@ -663,7 +661,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ - common.log('OnionShareGui', 'copy_hidservauth') + self.common.log('OnionShareGui', 'copy_hidservauth') if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) @@ -697,9 +695,9 @@ class OnionShareGui(QtWidgets.QMainWindow): Update the 'Downloads completed' info widget. """ if count == 0: - self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png') + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') else: - self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png') + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) @@ -708,17 +706,17 @@ class OnionShareGui(QtWidgets.QMainWindow): Update the 'Downloads in progress' info widget. """ if count == 0: - self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress_none.png') + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') else: - self.info_in_progress_downloads_image = common.get_resource_path('images/download_in_progress.png') + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) def closeEvent(self, e): - common.log('OnionShareGui', 'closeEvent') + self.common.log('OnionShareGui', 'closeEvent') try: if self.server_status.status != self.server_status.STATUS_STOPPED: - common.log('OnionShareGui', 'closeEvent, opening warning dialog') + self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) dialog.setText(strings._('gui_quit_warning', True)) @@ -803,9 +801,12 @@ class OnionThread(QtCore.QThread): decided to cancel (in which case do not proceed with obtaining the Onion address and starting the web server). """ - def __init__(self, function, kwargs=None): + def __init__(self, common, function, kwargs=None): super(OnionThread, self).__init__() - common.log('OnionThread', '__init__') + + self.common = common + + self.common.log('OnionThread', '__init__') self.function = function if not kwargs: self.kwargs = {} @@ -813,6 +814,6 @@ class OnionThread(QtCore.QThread): self.kwargs = kwargs def run(self): - common.log('OnionThread', 'run') + self.common.log('OnionThread', 'run') self.function(**self.kwargs) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 03540415..62df81ff 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -21,7 +21,7 @@ import platform from .alert import Alert from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common, settings +from onionshare import strings class ServerStatus(QtWidgets.QWidget): """ @@ -38,8 +38,11 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, qtapp, app, web, file_selection, settings): + def __init__(self, common, qtapp, app, web, file_selection, settings): super(ServerStatus, self).__init__() + + self.common = common + self.status = self.STATUS_STOPPED self.qtapp = qtapp @@ -129,7 +132,7 @@ class ServerStatus(QtWidgets.QWidget): if self.status == self.STATUS_STARTED: self.url_description.show() - info_image = common.get_resource_path('images/info.png') + info_image = self.common.get_resource_path('images/info.png') self.url_description.setText(strings._('gui_url_description', True).format(info_image)) # Show a Tool Tip explaining the lifecycle of this URL if self.settings.get('save_private_key'): @@ -212,7 +215,7 @@ class ServerStatus(QtWidgets.QWidget): self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) else: self.start_server() else: @@ -252,7 +255,7 @@ class ServerStatus(QtWidgets.QWidget): """ Cancel the server. """ - common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') self.status = self.STATUS_WORKING self.shutdown_timeout_reset() self.update() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7c81afc6..2bd20d84 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -34,9 +34,12 @@ class SettingsDialog(QtWidgets.QDialog): """ settings_saved = QtCore.pyqtSignal() - def __init__(self, onion, qtapp, config=False): + def __init__(self, common, onion, qtapp, config=False): super(SettingsDialog, self).__init__() - common.log('SettingsDialog', '__init__') + + self.common = common + + self.common.log('SettingsDialog', '__init__') self.onion = onion self.qtapp = qtapp @@ -44,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog): self.setModal(True) self.setWindowTitle(strings._('gui_settings_window_title', True)) - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.system = platform.system() @@ -156,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog): # obfs4 option radio # 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) = 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): 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.setEnabled(False) @@ -166,7 +169,7 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite-amazon option radio # if the obfs4proxy binary is missing, we can't use meek_lite-amazon transports - (self.tor_path, self.tor_geo_ip_file_path, self.tor_geo_ipv6_file_path, self.obfs4proxy_file_path) = 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): self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True)) self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False) @@ -176,7 +179,7 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite-azure option radio # 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) = 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): 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.setEnabled(False) @@ -330,7 +333,7 @@ class SettingsDialog(QtWidgets.QDialog): self.save_button.clicked.connect(self.save_clicked) self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) - version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(common.get_version())) + version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) version_label.setStyleSheet('color: #666666') self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) @@ -372,7 +375,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setFocus() # Load settings, and fill them in - self.old_settings = Settings(self.config) + self.old_settings = Settings(self.common, self.config) self.old_settings.load() close_after_first_download = self.old_settings.get('close_after_first_download') @@ -470,7 +473,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type bundled was toggled. If checked, hide authentication fields. """ - common.log('SettingsDialog', 'connection_type_bundled_toggled') + self.common.log('SettingsDialog', 'connection_type_bundled_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -515,7 +518,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Connection type automatic was toggled. If checked, hide authentication fields. """ - common.log('SettingsDialog', 'connection_type_automatic_toggled') + self.common.log('SettingsDialog', 'connection_type_automatic_toggled') if checked: self.authenticate_group.hide() self.connection_type_socks.hide() @@ -526,7 +529,7 @@ class SettingsDialog(QtWidgets.QDialog): Connection type control port was toggled. If checked, show extra fields for Tor control address and port. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'connection_type_control_port_toggled') + self.common.log('SettingsDialog', 'connection_type_control_port_toggled') if checked: self.authenticate_group.show() self.connection_type_control_port_extras.show() @@ -541,7 +544,7 @@ class SettingsDialog(QtWidgets.QDialog): Connection type socket file was toggled. If checked, show extra fields for socket file. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'connection_type_socket_file_toggled') + self.common.log('SettingsDialog', 'connection_type_socket_file_toggled') if checked: self.authenticate_group.show() self.connection_type_socket_file_extras.show() @@ -554,14 +557,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Authentication option no authentication was toggled. """ - common.log('SettingsDialog', 'authenticate_no_auth_toggled') + self.common.log('SettingsDialog', 'authenticate_no_auth_toggled') def authenticate_password_toggled(self, checked): """ Authentication option password was toggled. If checked, show extra fields for password auth. If unchecked, hide those extra fields. """ - common.log('SettingsDialog', 'authenticate_password_toggled') + self.common.log('SettingsDialog', 'authenticate_password_toggled') if checked: self.authenticate_password_extras.show() else: @@ -572,7 +575,7 @@ class SettingsDialog(QtWidgets.QDialog): Toggle the 'Copy HidServAuth' button to copy the saved HidServAuth to clipboard. """ - common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') + self.common.log('SettingsDialog', 'hidservauth_copy_button_clicked', 'HidServAuth was copied to clipboard') clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) @@ -581,7 +584,7 @@ class SettingsDialog(QtWidgets.QDialog): Test Tor Settings button clicked. With the given settings, see if we can successfully connect and authenticate to Tor. """ - common.log('SettingsDialog', 'test_tor_clicked') + self.common.log('SettingsDialog', 'test_tor_clicked') settings = self.settings_from_fields() try: @@ -600,13 +603,13 @@ class SettingsDialog(QtWidgets.QDialog): onion.connect(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 - Alert(strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) + Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) # Clean up onion.cleanup() except (TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: - Alert(e.args[0], QtWidgets.QMessageBox.Warning) + Alert(self.common, e.args[0], QtWidgets.QMessageBox.Warning) if settings.get('connection_type') == 'bundled': self.tor_status.hide() self._enable_buttons() @@ -615,14 +618,14 @@ class SettingsDialog(QtWidgets.QDialog): """ Check for Updates button clicked. Manually force an update check. """ - common.log('SettingsDialog', 'check_for_updates') + self.common.log('SettingsDialog', 'check_for_updates') # Disable buttons self._disable_buttons() self.qtapp.processEvents() def update_timestamp(): # Update the last checked label - settings = Settings(self.config) + settings = Settings(self.common, self.config) settings.load() autoupdate_timestamp = settings.get('autoupdate_timestamp') self._update_autoupdate_timestamp(autoupdate_timestamp) @@ -636,22 +639,22 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates def update_available(update_url, installed_version, latest_version): - Alert(strings._("update_available", True).format(update_url, installed_version, latest_version)) + Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) close_forced_update_thread() def update_not_available(): - Alert(strings._('update_not_available', True)) + Alert(self.common, strings._('update_not_available', True)) close_forced_update_thread() def update_error(): - Alert(strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) close_forced_update_thread() def update_invalid_version(): - Alert(strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) close_forced_update_thread() - forced_update_thread = UpdateThread(self.onion, self.config, force=True) + forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) forced_update_thread.update_available.connect(update_available) forced_update_thread.update_not_available.connect(update_not_available) forced_update_thread.update_error.connect(update_error) @@ -662,7 +665,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Save button clicked. Save current settings to disk. """ - common.log('SettingsDialog', 'save_clicked') + self.common.log('SettingsDialog', 'save_clicked') settings = self.settings_from_fields() if settings: @@ -672,7 +675,7 @@ class SettingsDialog(QtWidgets.QDialog): # the Onion object reboot_onion = False if self.onion.is_authenticated(): - common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values @@ -694,20 +697,20 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = True else: - common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object - common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() - tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, settings, self.onion) tor_con.start() - common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() @@ -721,9 +724,9 @@ class SettingsDialog(QtWidgets.QDialog): """ Cancel button clicked. """ - common.log('SettingsDialog', 'cancel_clicked') + self.common.log('SettingsDialog', 'cancel_clicked') if not self.onion.is_authenticated(): - Alert(strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: self.close() @@ -732,7 +735,7 @@ class SettingsDialog(QtWidgets.QDialog): """ Help button clicked. """ - common.log('SettingsDialog', 'help_clicked') + self.common.log('SettingsDialog', 'help_clicked') help_site = 'https://github.com/micahflee/onionshare/wiki' QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) @@ -740,8 +743,8 @@ class SettingsDialog(QtWidgets.QDialog): """ Return a Settings object that's full of values from the settings dialog. """ - common.log('SettingsDialog', 'settings_from_fields') - settings = Settings(self.config) + self.common.log('SettingsDialog', 'settings_from_fields') + settings = Settings(self.common, self.config) settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) @@ -846,24 +849,24 @@ class SettingsDialog(QtWidgets.QDialog): new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: - Alert(strings._('gui_settings_tor_bridges_invalid', True)) + Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True)) settings.set('no_bridges', True) return False return settings def closeEvent(self, e): - common.log('SettingsDialog', 'closeEvent') + self.common.log('SettingsDialog', 'closeEvent') # On close, if Tor isn't connected, then quit OnionShare altogether if not self.onion.is_authenticated(): - common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') + self.common.log('SettingsDialog', 'closeEvent', 'Closing while not connected to Tor') # Wait 1ms for the event loop to finish, then quit QtCore.QTimer.singleShot(1, self.qtapp.quit) def _update_autoupdate_timestamp(self, autoupdate_timestamp): - common.log('SettingsDialog', '_update_autoupdate_timestamp') + self.common.log('SettingsDialog', '_update_autoupdate_timestamp') if autoupdate_timestamp: dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) @@ -880,7 +883,7 @@ class SettingsDialog(QtWidgets.QDialog): self._enable_buttons() def _disable_buttons(self): - common.log('SettingsDialog', '_disable_buttons') + self.common.log('SettingsDialog', '_disable_buttons') self.check_for_updates_button.setEnabled(False) self.connection_type_test_button.setEnabled(False) @@ -888,7 +891,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button.setEnabled(False) def _enable_buttons(self): - common.log('SettingsDialog', '_enable_buttons') + self.common.log('SettingsDialog', '_enable_buttons') # We can't check for updates if we're still not connected to Tor if not self.onion.connected_to_tor: self.check_for_updates_button.setEnabled(False) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index dc472725..6d127df9 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -19,7 +19,7 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common +from onionshare import strings from onionshare.onion import * from .alert import Alert @@ -30,16 +30,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): """ open_settings = QtCore.pyqtSignal() - def __init__(self, qtapp, settings, onion): + def __init__(self, common, qtapp, settings, onion): super(TorConnectionDialog, self).__init__(None) - common.log('TorConnectionDialog', '__init__') + + self.common = common + + self.common.log('TorConnectionDialog', '__init__') self.qtapp = qtapp self.settings = settings self.onion = onion self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setModal(True) self.setFixedSize(400, 150) @@ -55,9 +58,9 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self._tor_status_update(0, '') def start(self): - common.log('TorConnectionDialog', 'start') + self.common.log('TorConnectionDialog', 'start') - t = TorConnectionThread(self, self.settings, self.onion) + t = TorConnectionThread(self.common, self, self.settings, self.onion) t.tor_status_update.connect(self._tor_status_update) t.connected_to_tor.connect(self._connected_to_tor) t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor) @@ -77,14 +80,14 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setLabelText("{}
    {}".format(strings._('connecting_to_tor', True), summary)) def _connected_to_tor(self): - common.log('TorConnectionDialog', '_connected_to_tor') + self.common.log('TorConnectionDialog', '_connected_to_tor') self.active = False # Close the dialog after connecting self.setValue(self.maximum()) def _canceled_connecting_to_tor(self): - common.log('TorConnectionDialog', '_canceled_connecting_to_tor') + self.common.log('TorConnectionDialog', '_canceled_connecting_to_tor') self.active = False self.onion.cleanup() @@ -92,12 +95,12 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): QtCore.QTimer.singleShot(1, self.cancel) def _error_connecting_to_tor(self, msg): - common.log('TorConnectionDialog', '_error_connecting_to_tor') + self.common.log('TorConnectionDialog', '_error_connecting_to_tor') self.active = False def alert_and_open_settings(): # Display the exception in an alert box - Alert("{}\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', True)), QtWidgets.QMessageBox.Warning) # Open settings self.open_settings.emit() @@ -113,16 +116,19 @@ class TorConnectionThread(QtCore.QThread): canceled_connecting_to_tor = QtCore.pyqtSignal() error_connecting_to_tor = QtCore.pyqtSignal(str) - def __init__(self, dialog, settings, onion): + def __init__(self, common, dialog, settings, onion): super(TorConnectionThread, self).__init__() - common.log('TorConnectionThread', '__init__') + + self.common = common + + self.common.log('TorConnectionThread', '__init__') self.dialog = dialog self.settings = settings self.onion = onion def run(self): - common.log('TorConnectionThread', 'run') + self.common.log('TorConnectionThread', 'run') # Connect to the Onion try: @@ -133,11 +139,11 @@ class TorConnectionThread(QtCore.QThread): self.canceled_connecting_to_tor.emit() except BundledTorCanceled as e: - common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') + self.common.log('TorConnectionThread', 'run', 'caught exception: BundledTorCanceled') self.canceled_connecting_to_tor.emit() except Exception as e: - common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) + self.common.log('TorConnectionThread', 'run', 'caught exception: {}'.format(e.args[0])) self.error_connecting_to_tor.emit(str(e.args[0])) def _tor_status_update(self, progress, summary): diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 8b4884a2..5dc72091 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -25,7 +25,7 @@ from onionshare import socks from onionshare.settings import Settings from onionshare.onion import Onion -from . import strings, common +from . import strings class UpdateCheckerCheckError(Exception): """ @@ -55,16 +55,19 @@ class UpdateChecker(QtCore.QObject): update_error = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal() - def __init__(self, onion, config=False): + def __init__(self, common, onion, config=False): super(UpdateChecker, self).__init__() - common.log('UpdateChecker', '__init__') + + self.common = common + + self.common.log('UpdateChecker', '__init__') self.onion = onion self.config = config def check(self, force=False, config=False): - common.log('UpdateChecker', 'check', 'force={}'.format(force)) + self.common.log('UpdateChecker', 'check', 'force={}'.format(force)) # Load the settings - settings = Settings(config) + settings = Settings(self.common, config) settings.load() # If force=True, then definitely check @@ -87,11 +90,11 @@ class UpdateChecker(QtCore.QObject): # Check for updates if check_for_updates: - common.log('UpdateChecker', 'check', 'checking for updates') + self.common.log('UpdateChecker', 'check', 'checking for updates') # Download the latest-version file over Tor try: # User agent string includes OnionShare version and platform - user_agent = 'OnionShare {}, {}'.format(common.get_version(), platform.system()) + user_agent = 'OnionShare {}, {}'.format(self.common.version, self.common.platform) # If the update is forced, add '?force=1' to the URL, to more # accurately measure daily users @@ -104,7 +107,7 @@ class UpdateChecker(QtCore.QObject): else: onion_domain = 'elx57ue5uyfplgva.onion' - common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) + self.common.log('UpdateChecker', 'check', 'loading http://{}{}'.format(onion_domain, path)) (socks_address, socks_port) = self.onion.get_tor_socks_port() socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) @@ -122,10 +125,10 @@ class UpdateChecker(QtCore.QObject): http_response = s.recv(1024) latest_version = http_response[http_response.find(b'\r\n\r\n'):].strip().decode('utf-8') - common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) + self.common.log('UpdateChecker', 'check', 'latest OnionShare version: {}'.format(latest_version)) except Exception as e: - common.log('UpdateChecker', 'check', '{}'.format(e)) + self.common.log('UpdateChecker', 'check', '{}'.format(e)) self.update_error.emit() raise UpdateCheckerCheckError @@ -145,7 +148,7 @@ class UpdateChecker(QtCore.QObject): # Do we need to update? update_url = 'https://github.com/micahflee/onionshare/releases/tag/v{}'.format(latest_version) - installed_version = common.get_version() + installed_version = self.common.version if installed_version < latest_version: self.update_available.emit(update_url, installed_version, latest_version) return @@ -159,17 +162,20 @@ class UpdateThread(QtCore.QThread): update_error = QtCore.pyqtSignal() update_invalid_version = QtCore.pyqtSignal() - def __init__(self, onion, config=False, force=False): + def __init__(self, common, onion, config=False, force=False): super(UpdateThread, self).__init__() - common.log('UpdateThread', '__init__') + + self.common = common + + self.common.log('UpdateThread', '__init__') self.onion = onion self.config = config self.force = force def run(self): - common.log('UpdateThread', 'run') + self.common.log('UpdateThread', 'run') - u = UpdateChecker(self.onion, self.config) + u = UpdateChecker(self.common, self.onion, self.config) u.update_available.connect(self._update_available) u.update_not_available.connect(self._update_not_available) u.update_error.connect(self._update_error) @@ -179,25 +185,25 @@ class UpdateThread(QtCore.QThread): u.check(config=self.config,force=self.force) except Exception as e: # If update check fails, silently ignore - common.log('UpdateThread', 'run', '{}'.format(e)) + self.common.log('UpdateThread', 'run', '{}'.format(e)) pass def _update_available(self, update_url, installed_version, latest_version): - common.log('UpdateThread', '_update_available') + self.common.log('UpdateThread', '_update_available') self.active = False self.update_available.emit(update_url, installed_version, latest_version) def _update_not_available(self): - common.log('UpdateThread', '_update_not_available') + self.common.log('UpdateThread', '_update_not_available') self.active = False self.update_not_available.emit() def _update_error(self): - common.log('UpdateThread', '_update_error') + self.common.log('UpdateThread', '_update_error') self.active = False self.update_error.emit() def _update_invalid_version(self): - common.log('UpdateThread', '_update_invalid_version') + self.common.log('UpdateThread', '_update_invalid_version') self.active = False self.update_invalid_version.emit() diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index 66e4a808..ae8e4217 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -157,13 +157,13 @@ class TestGetAvailablePort: class TestGetPlatform: def test_darwin(self, platform_darwin): - assert common.get_platform() == 'Darwin' + assert common.platform == 'Darwin' def test_linux(self, platform_linux): - assert common.get_platform() == 'Linux' + assert common.platform == 'Linux' def test_windows(self, platform_windows): - assert common.get_platform() == 'Windows' + assert common.platform == 'Windows' # TODO: double-check these tests @@ -235,14 +235,6 @@ class TestGetTorPaths: (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) -class TestGetVersion: - def test_get_version(self, sys_onionshare_dev_mode): - with open(common.get_resource_path('version.txt')) as f: - version = f.read().strip() - - assert version == common.get_version() - - class TestHumanReadableFilesize: @pytest.mark.parametrize('test_input,expected', ( (1024 ** 0, '1.0 B'), @@ -284,13 +276,3 @@ class TestLog: line_one, line_two, _ = output.split('\n') assert LOG_MSG_REGEX.match(line_one) assert LOG_MSG_REGEX.match(line_two) - - -class TestSetDebug: - def test_debug_true(self, set_debug_false): - common.set_debug(True) - assert common.debug is True - - def test_debug_false(self, set_debug_true): - common.set_debug(False) - assert common.debug is False diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 88997749..377fba16 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -28,7 +28,7 @@ from onionshare import common, settings, strings @pytest.fixture def custom_version(monkeypatch): - monkeypatch.setattr(common, 'get_version', lambda: 'DUMMY_VERSION_1.2.3') + monkeypatch.setattr(common, 'version', 'DUMMY_VERSION_1.2.3') @pytest.fixture -- cgit v1.2.3-54-g00ecf From c2fecf8aa47af69636eba0b46366f0e430fc8250 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 13 Mar 2018 02:22:26 -0700 Subject: Fix tests after refactoring Common --- onionshare/common.py | 2 +- test/conftest.py | 19 ++++----- test/test_onionshare.py | 4 +- test/test_onionshare_common.py | 85 +++++++++++++++++++++------------------- test/test_onionshare_settings.py | 20 ++++------ test/test_onionshare_strings.py | 18 ++++----- test/test_onionshare_web.py | 4 +- 7 files changed, 73 insertions(+), 79 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 36848738..c24a1b4b 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -176,7 +176,7 @@ class Common(object): download_rate = bytes_downloaded / time_elapsed remaining_bytes = total_bytes - bytes_downloaded eta = remaining_bytes / download_rate - return format_seconds(eta) + return Common.format_seconds(eta) @staticmethod def get_available_port(min_port, max_port): diff --git a/test/conftest.py b/test/conftest.py index 88a7c054..e843bbbc 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -65,7 +65,8 @@ def temp_file_1024_delete(): @pytest.yield_fixture(scope='session') def custom_zw(): zw = web.ZipWriter( - zip_filename=common.random_string(4, 6), + common.Common(), + zip_filename=common.Common.random_string(4, 6), processed_size_callback=lambda _: 'custom_callback' ) yield zw @@ -76,7 +77,7 @@ def custom_zw(): # pytest > 2.9 only needs @pytest.fixture @pytest.yield_fixture(scope='session') def default_zw(): - zw = web.ZipWriter() + zw = web.ZipWriter(common.Common()) yield zw zw.close() tmp_dir = os.path.dirname(zw.zip_filename) @@ -118,16 +119,6 @@ def platform_windows(monkeypatch): monkeypatch.setattr('platform.system', lambda: 'Windows') -@pytest.fixture -def set_debug_false(monkeypatch): - monkeypatch.setattr('onionshare.common.debug', False) - - -@pytest.fixture -def set_debug_true(monkeypatch): - monkeypatch.setattr('onionshare.common.debug', True) - - @pytest.fixture def sys_argv_sys_prefix(monkeypatch): monkeypatch.setattr('sys.argv', [sys.prefix]) @@ -157,3 +148,7 @@ def time_time_100(monkeypatch): @pytest.fixture def time_strftime(monkeypatch): monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() diff --git a/test/test_onionshare.py b/test/test_onionshare.py index 76e471bd..398fd0d3 100644 --- a/test/test_onionshare.py +++ b/test/test_onionshare.py @@ -22,6 +22,7 @@ import os import pytest from onionshare import OnionShare +from onionshare.common import Common class MyOnion: @@ -37,7 +38,8 @@ class MyOnion: @pytest.fixture def onionshare_obj(): - return OnionShare(MyOnion()) + common = Common() + return OnionShare(common, MyOnion()) class TestOnionShare: diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index ae8e4217..c0f9ad66 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -29,8 +29,6 @@ import zipfile import pytest -from onionshare import common - LOG_MSG_REGEX = re.compile(r""" ^\[Jun\ 06\ 2013\ 11:05:00\] \ TestModule\.\.dummy_func @@ -38,6 +36,9 @@ LOG_MSG_REGEX = re.compile(r""" SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') +# TODO: Improve the Common tests to test it all as a single class + + class TestBuildSlug: @pytest.mark.parametrize('test_input,expected', ( # VALID, two lowercase words, separated by a hyphen @@ -77,17 +78,17 @@ class TestBuildSlug: assert bool(SLUG_REGEX.match(test_input)) == expected - def test_build_slug_unique(self, sys_onionshare_dev_mode): - assert common.build_slug() != common.build_slug() + def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode): + assert common_obj.build_slug() != common_obj.build_slug() class TestDirSize: - def test_temp_dir_size(self, temp_dir_1024_delete): + def test_temp_dir_size(self, common_obj, temp_dir_1024_delete): """ dir_size() should return the total size (in bytes) of all files in a particular directory. """ - assert common.dir_size(temp_dir_1024_delete) == 1024 + assert common_obj.dir_size(temp_dir_1024_delete) == 1024 class TestEstimatedTimeRemaining: @@ -101,16 +102,16 @@ class TestEstimatedTimeRemaining: ((971, 1009, 83), '1s') )) def test_estimated_time_remaining( - self, test_input, expected, time_time_100): - assert common.estimated_time_remaining(*test_input) == expected + self, common_obj, test_input, expected, time_time_100): + assert common_obj.estimated_time_remaining(*test_input) == expected @pytest.mark.parametrize('test_input', ( (10, 20, 100), # if `time_elapsed == 0` (0, 37, 99) # if `download_rate == 0` )) - def test_raises_zero_division_error(self, test_input, time_time_100): + def test_raises_zero_division_error(self, common_obj, test_input, time_time_100): with pytest.raises(ZeroDivisionError): - common.estimated_time_remaining(*test_input) + common_obj.estimated_time_remaining(*test_input) class TestFormatSeconds: @@ -129,16 +130,16 @@ class TestFormatSeconds: (129674, '1d12h1m14s'), (56404.12, '15h40m4s') )) - def test_format_seconds(self, test_input, expected): - assert common.format_seconds(test_input) == expected + def test_format_seconds(self, common_obj, test_input, expected): + assert common_obj.format_seconds(test_input) == expected # TODO: test negative numbers? @pytest.mark.parametrize('test_input', ( 'string', lambda: None, [], {}, set() )) - def test_invalid_input_types(self, test_input): + def test_invalid_input_types(self, common_obj, test_input): with pytest.raises(TypeError): - common.format_seconds(test_input) + common_obj.format_seconds(test_input) class TestGetAvailablePort: @@ -146,29 +147,29 @@ class TestGetAvailablePort: (random.randint(1024, 1500), random.randint(1800, 2048)) for _ in range(50) )) - def test_returns_an_open_port(self, port_min, port_max): + def test_returns_an_open_port(self, common_obj, port_min, port_max): """ get_available_port() should return an open port within the range """ - port = common.get_available_port(port_min, port_max) + port = common_obj.get_available_port(port_min, port_max) assert port_min <= port <= port_max with socket.socket() as tmpsock: tmpsock.bind(('127.0.0.1', port)) class TestGetPlatform: - def test_darwin(self, platform_darwin): - assert common.platform == 'Darwin' + def test_darwin(self, platform_darwin, common_obj): + assert common_obj.platform == 'Darwin' - def test_linux(self, platform_linux): - assert common.platform == 'Linux' + def test_linux(self, platform_linux, common_obj): + assert common_obj.platform == 'Linux' - def test_windows(self, platform_windows): - assert common.platform == 'Windows' + def test_windows(self, platform_windows, common_obj): + assert common_obj.platform == 'Windows' # TODO: double-check these tests class TestGetResourcePath: - def test_onionshare_dev_mode(self, sys_onionshare_dev_mode): + def test_onionshare_dev_mode(self, common_obj, sys_onionshare_dev_mode): prefix = os.path.join( os.path.dirname( os.path.dirname( @@ -176,29 +177,29 @@ class TestGetResourcePath: inspect.getfile( inspect.currentframe())))), 'share') assert ( - common.get_resource_path(os.path.join(prefix, 'test_filename')) == + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == os.path.join(prefix, 'test_filename')) - def test_linux(self, platform_linux, sys_argv_sys_prefix): + def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix): prefix = os.path.join(sys.prefix, 'share/onionshare') assert ( - common.get_resource_path(os.path.join(prefix, 'test_filename')) == + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == os.path.join(prefix, 'test_filename')) - def test_frozen_darwin(self, platform_darwin, sys_frozen, sys_meipass): + def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass): prefix = os.path.join(sys._MEIPASS, 'share') assert ( - common.get_resource_path(os.path.join(prefix, 'test_filename')) == + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == os.path.join(prefix, 'test_filename')) class TestGetTorPaths: # @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ? - def test_get_tor_paths_darwin(self, platform_darwin, sys_frozen, sys_meipass): + def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass): base_path = os.path.dirname( os.path.dirname( os.path.dirname( - common.get_resource_path('')))) + common_obj.get_resource_path('')))) tor_path = os.path.join( base_path, 'Resources', 'Tor', 'tor') tor_geo_ip_file_path = os.path.join( @@ -207,20 +208,20 @@ class TestGetTorPaths: base_path, 'Resources', 'Tor', 'geoip6') obfs4proxy_file_path = os.path.join( base_path, 'Resources', 'Tor', 'obfs4proxy') - assert (common.get_tor_paths() == + assert (common_obj.get_tor_paths() == (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) # @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ? - def test_get_tor_paths_linux(self, platform_linux): - assert (common.get_tor_paths() == + def test_get_tor_paths_linux(self, platform_linux, common_obj): + assert (common_obj.get_tor_paths() == ('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy')) # @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ? - def test_get_tor_paths_windows(self, platform_windows, sys_frozen): + def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen): base_path = os.path.join( os.path.dirname( os.path.dirname( - common.get_resource_path(''))), 'tor') + common_obj.get_resource_path(''))), 'tor') tor_path = os.path.join( os.path.join(base_path, 'Tor'), 'tor.exe') obfs4proxy_file_path = os.path.join( @@ -231,7 +232,7 @@ class TestGetTorPaths: tor_geo_ipv6_file_path = os.path.join( os.path.join( os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - assert (common.get_tor_paths() == + assert (common_obj.get_tor_paths() == (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) @@ -247,8 +248,8 @@ class TestHumanReadableFilesize: (1024 ** 7, '1.0 ZiB'), (1024 ** 8, '1.0 YiB') )) - def test_human_readable_filesize(self, test_input, expected): - assert common.human_readable_filesize(test_input) == expected + def test_human_readable_filesize(self, common_obj, test_input, expected): + assert common_obj.human_readable_filesize(test_input) == expected class TestLog: @@ -263,14 +264,16 @@ class TestLog: def test_log_msg_regex(self, test_input): assert bool(LOG_MSG_REGEX.match(test_input)) - def test_output(self, set_debug_true, time_strftime): + def test_output(self, common_obj, time_strftime): def dummy_func(): pass + common_obj.debug = True + # From: https://stackoverflow.com/questions/1218933 with io.StringIO() as buf, contextlib.redirect_stdout(buf): - common.log('TestModule', dummy_func) - common.log('TestModule', dummy_func, 'TEST_MSG') + common_obj.log('TestModule', dummy_func) + common_obj.log('TestModule', dummy_func, 'TEST_MSG') output = buf.getvalue() line_one, line_two, _ = output.split('\n') diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 377fba16..67fd7b38 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -26,19 +26,16 @@ import pytest from onionshare import common, settings, strings -@pytest.fixture -def custom_version(monkeypatch): - monkeypatch.setattr(common, 'version', 'DUMMY_VERSION_1.2.3') - - @pytest.fixture def os_path_expanduser(monkeypatch): monkeypatch.setattr('os.path.expanduser', lambda path: path) @pytest.fixture -def settings_obj(custom_version, sys_onionshare_dev_mode, platform_linux): - return settings.Settings() +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) class TestSettings: @@ -154,30 +151,27 @@ class TestSettings: def test_filename_darwin( self, - custom_version, monkeypatch, os_path_expanduser, platform_darwin): - obj = settings.Settings() + obj = settings.Settings(common.Common()) assert (obj.filename == '~/Library/Application Support/OnionShare/onionshare.json') def test_filename_linux( self, - custom_version, monkeypatch, os_path_expanduser, platform_linux): - obj = settings.Settings() + obj = settings.Settings(common.Common()) assert obj.filename == '~/.config/onionshare/onionshare.json' def test_filename_windows( self, - custom_version, monkeypatch, platform_windows): monkeypatch.setenv('APPDATA', 'C:') - obj = settings.Settings() + obj = settings.Settings(common.Common()) assert obj.filename == 'C:\\OnionShare\\onionshare.json' def test_set_custom_bridge(self, settings_obj): diff --git a/test/test_onionshare_strings.py b/test/test_onionshare_strings.py index d9fa9896..db941a26 100644 --- a/test/test_onionshare_strings.py +++ b/test/test_onionshare_strings.py @@ -22,7 +22,7 @@ import types import pytest -from onionshare import common, strings +from onionshare import strings # # Stub get_resource_path so it finds the correct path while running tests @@ -44,28 +44,28 @@ def test_underscore_is_function(): class TestLoadStrings: def test_load_strings_defaults_to_english( - self, locale_en, sys_onionshare_dev_mode): + self, common_obj, locale_en, sys_onionshare_dev_mode): """ load_strings() loads English by default """ - strings.load_strings(common) + strings.load_strings(common_obj) assert strings._('wait_for_hs') == "Waiting for HS to be ready:" def test_load_strings_loads_other_languages( - self, locale_fr, sys_onionshare_dev_mode): + self, common_obj, locale_fr, sys_onionshare_dev_mode): """ load_strings() loads other languages in different locales """ - strings.load_strings(common, "fr") + strings.load_strings(common_obj, "fr") assert strings._('wait_for_hs') == "En attente du HS:" def test_load_partial_strings( - self, locale_ru, sys_onionshare_dev_mode): - strings.load_strings(common) + self, common_obj, locale_ru, sys_onionshare_dev_mode): + strings.load_strings(common_obj) assert strings._("give_this_url") == ( "Отправьте эту ссылку тому человеку, " "которому вы хотите передать файл:") assert strings._('wait_for_hs') == "Waiting for HS to be ready:" def test_load_invalid_locale( - self, locale_invalid, sys_onionshare_dev_mode): + self, common_obj, locale_invalid, sys_onionshare_dev_mode): """ load_strings() raises a KeyError for an invalid locale """ with pytest.raises(KeyError): - strings.load_strings(common, 'XX') + strings.load_strings(common_obj, 'XX') diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 55ff1f85..a80e7098 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -29,7 +29,7 @@ import zipfile import pytest -from onionshare import common +from onionshare.common import Common DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') @@ -75,7 +75,7 @@ class TestZipWriterDefault: class TestZipWriterCustom: @pytest.mark.parametrize('test_input', ( - common.random_string( + Common.random_string( random.randint(2, 50), random.choice((None, random.randint(2, 50))) ) for _ in range(50) -- cgit v1.2.3-54-g00ecf From 76d299a6c90f4fc7a899e3c19da7ac6f23c1389e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 13 Mar 2018 03:28:47 -0700 Subject: Move settings into the Common object, so the settings are available to all objects (including Web, which is required for receive mode) --- onionshare/__init__.py | 30 +++++++++++++-------------- onionshare/common.py | 9 +++++++++ onionshare/onion.py | 11 +++++----- onionshare_gui/__init__.py | 1 - onionshare_gui/onionshare_gui.py | 36 ++++++++++++++++----------------- onionshare_gui/server_status.py | 30 +++++++++++++-------------- onionshare_gui/settings_dialog.py | 6 +++--- onionshare_gui/tor_connection_dialog.py | 15 +++++++++----- 8 files changed, 72 insertions(+), 66 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 1e07f11c..5b23ad3e 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -25,7 +25,6 @@ from .common import Common from .web import Web from .onion import * from .onionshare import OnionShare -from .settings import Settings def main(cwd=None): """ @@ -71,9 +70,6 @@ def main(cwd=None): print(strings._('no_filenames')) sys.exit() - # Debug mode? - common.debug = debug - # Validate filenames if not receive: valid = True @@ -88,20 +84,22 @@ def main(cwd=None): sys.exit() # Load settings - settings = Settings(common, config) - settings.load() + common.load_settings(config) + + # Debug mode? + common.debug = debug # In receive mode, validate downloads dir if receive: valid = True - if not os.path.isdir(settings.get('downloads_dir')): + if not os.path.isdir(common.settings.get('downloads_dir')): try: - os.mkdir(settings.get('downloads_dir'), 0o700) + os.mkdir(common.settings.get('downloads_dir'), 0o700) except: - print(strings._('error_cannot_create_downloads_dir').format(settings.get('downloads_dir'))) + print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir'))) valid = False if valid and not os.access(settings.get('downloads_dir'), os.W_OK): - print(strings._('error_downloads_dir_not_writable').format(settings.get('downloads_dir'))) + print(strings._('error_downloads_dir_not_writable').format(common.settings.get('downloads_dir'))) valid = False if not valid: sys.exit() @@ -112,7 +110,7 @@ def main(cwd=None): # Start the Onion object onion = Onion(common) try: - onion.connect(settings=False, config=config) + onion.connect(custom_settings=False, config=config) except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: sys.exit(e.args[0]) except KeyboardInterrupt: @@ -144,7 +142,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, app.stay_open, settings.get('slug'))) + t = threading.Thread(target=web.start, args=(app.port, app.stay_open, common.settings.get('slug'))) t.daemon = True t.start() @@ -157,10 +155,10 @@ def main(cwd=None): app.shutdown_timer.start() # Save the web slug if we are using a persistent private key - if settings.get('save_private_key'): - if not settings.get('slug'): - settings.set('slug', web.slug) - settings.save() + if common.settings.get('save_private_key'): + if not common.settings.get('slug'): + common.settings.set('slug', web.slug) + common.settings.save() print('') if receive: diff --git a/onionshare/common.py b/onionshare/common.py index c24a1b4b..903e4148 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -29,6 +29,8 @@ import tempfile import threading import time +from .settings import Settings + class Common(object): """ The Common object is shared amongst all parts of OnionShare. @@ -45,6 +47,13 @@ class Common(object): with open(self.get_resource_path('version.txt')) as f: self.version = f.read().strip() + def load_settings(self, config=None): + """ + Loading settings, optionally from a custom config json file. + """ + self.settings = Settings(self, config) + self.settings.load() + def log(self, module, func, msg=None): """ If debug mode is on, log error messages to stdout diff --git a/onionshare/onion.py b/onionshare/onion.py index 4b3b0971..57e407ea 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -148,15 +148,14 @@ class Onion(object): # Start out not connected to Tor self.connected_to_tor = False - def connect(self, settings=False, config=False, tor_status_update_func=None): + def connect(self, custom_settings=False, config=False, tor_status_update_func=None): self.common.log('Onion', 'connect') - # Either use settings that are passed in, or load them from disk - if settings: - self.settings = settings + # Either use settings that are passed in, or use them from common + if custom_settings: + self.settings = custom_settings else: - self.settings = Settings(self.common, config) - self.settings.load() + self.settings = self.common.settings # The Tor controller self.c = None diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 04fe1e62..e1ad8743 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -27,7 +27,6 @@ from onionshare.common import Common from onionshare.web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare -from onionshare.settings import Settings from .onionshare_gui import OnionShareGui diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 04b8a066..a52f232a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -25,7 +25,6 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings, common from onionshare.common import Common, ShutdownTimer -from onionshare.settings import Settings from onionshare.onion import * from .tor_connection_dialog import TorConnectionDialog @@ -66,8 +65,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Load settings self.config = config - self.settings = Settings(self.common, self.config) - self.settings.load() + self.common.load_settings(self.config) # File selection self.file_selection = FileSelection(self.common) @@ -76,7 +74,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.file_selection.file_list.add_file(filename) # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection, self.settings) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.update_server_status_indicator) @@ -222,7 +220,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.timeout.connect(self.check_for_requests) # Start the "Connecting to Tor" dialog, which calls onion.connect() - tor_con = TorConnectionDialog(self.common, self.qtapp, self.settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion) tor_con.canceled.connect(self._tor_connection_canceled) tor_con.open_settings.connect(self._tor_connection_open_settings) tor_con.start() @@ -339,7 +337,7 @@ class OnionShareGui(QtWidgets.QMainWindow): def reload_settings(): self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') - self.settings.load() + self.common.settings.load() # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. @@ -352,7 +350,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status.server_button.setEnabled(True) self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. - if not self.settings.get('shutdown_timeout'): + if not self.common.settings.get('shutdown_timeout'): self.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.common, self.onion, self.qtapp, self.config) @@ -371,7 +369,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.set_server_active(True) - self.app.set_stealth(self.settings.get('use_stealth')) + self.app.set_stealth(self.common.settings.get('use_stealth')) # Hide and reset the downloads if we have previously shared self.downloads_container.hide() @@ -395,10 +393,10 @@ class OnionShareGui(QtWidgets.QMainWindow): return - self.app.stay_open = not self.settings.get('close_after_first_download') + self.app.stay_open = not self.common.settings.get('close_after_first_download') # start onionshare http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.settings.get('slug'))) + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) t.daemon = True t.start() # wait for modules in thread to load, preventing a thread-related cx_Freeze crash @@ -462,7 +460,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() self.timeout = now.secsTo(self.server_status.timeout) @@ -527,7 +525,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for updates in a new thread, if enabled. """ if self.common.platform == 'Windows' or self.common.platform == 'Darwin': - if self.settings.get('use_autoupdate'): + if self.common.settings.get('use_autoupdate'): def update_available(update_url, installed_version, latest_version): Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version)) @@ -558,7 +556,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status.stop_server() self.server_status.server_button.setEnabled(False) self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane @@ -587,7 +585,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) elif event["type"] == self.web.REQUEST_RATE_LIMIT: @@ -599,7 +597,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # is the download complete? if event["data"]["bytes"] == self.web.zip_filesize: - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info self.downloads_completed += 1 @@ -625,7 +623,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) elif event["path"] != '/favicon.ico': @@ -633,7 +631,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.settings.get('shutdown_timeout'): + if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): if self.timeout > 0: now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) @@ -654,7 +652,7 @@ class OnionShareGui(QtWidgets.QMainWindow): When the URL gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_url') - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): @@ -662,7 +660,7 @@ class OnionShareGui(QtWidgets.QMainWindow): When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_hidservauth') - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 62df81ff..ed8bc5f5 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -38,7 +38,7 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, file_selection, settings): + def __init__(self, common, qtapp, app, web, file_selection): super(ServerStatus, self).__init__() self.common = common @@ -50,8 +50,6 @@ class ServerStatus(QtWidgets.QWidget): self.web = web self.file_selection = file_selection - self.settings = settings - # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -135,13 +133,13 @@ class ServerStatus(QtWidgets.QWidget): info_image = self.common.get_resource_path('images/info.png') self.url_description.setText(strings._('gui_url_description', True).format(info_image)) # Show a Tool Tip explaining the lifecycle of this URL - if self.settings.get('save_private_key'): - if self.settings.get('close_after_first_download'): + if self.common.settings.get('save_private_key'): + if self.common.settings.get('close_after_first_download'): self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) else: self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) else: - if self.settings.get('close_after_first_download'): + if self.common.settings.get('close_after_first_download'): self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) @@ -151,12 +149,12 @@ class ServerStatus(QtWidgets.QWidget): self.copy_url_button.show() - if self.settings.get('save_private_key'): - if not self.settings.get('slug'): - self.settings.set('slug', self.web.slug) - self.settings.save() + if self.common.settings.get('save_private_key'): + if not self.common.settings.get('slug'): + self.common.settings.set('slug', self.web.slug) + self.common.settings.save() - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() if self.app.stealth: @@ -183,26 +181,26 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_start_server', True)) self.server_button.setToolTip('') - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() elif self.status == self.STATUS_STARTED: self.server_button.setStyleSheet(button_started_style) self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_stop_server', True)) - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) elif self.status == self.STATUS_WORKING: self.server_button.setStyleSheet(button_working_style) self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_please_wait')) - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() else: self.server_button.setStyleSheet(button_working_style) self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() def server_button_clicked(self): @@ -210,7 +208,7 @@ class ServerStatus(QtWidgets.QWidget): Toggle starting or stopping the server. """ if self.status == self.STATUS_STOPPED: - if self.settings.get('shutdown_timeout'): + if self.common.settings.get('shutdown_timeout'): # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 2bd20d84..192815da 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -599,8 +599,8 @@ class SettingsDialog(QtWidgets.QDialog): else: tor_status_update_func = None - onion = Onion() - onion.connect(settings=settings, config=self.config, tor_status_update_func=tor_status_update_func) + onion = Onion(self.common) + 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 Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) @@ -707,7 +707,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() - tor_con = TorConnectionDialog(self.common, self.qtapp, settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) tor_con.start() self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 6d127df9..2ee13a66 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -30,15 +30,19 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): """ open_settings = QtCore.pyqtSignal() - def __init__(self, common, qtapp, settings, onion): + def __init__(self, common, qtapp, onion, custom_settings=False): super(TorConnectionDialog, self).__init__(None) self.common = common + if custom_settings: + self.settings = custom_settings + else: + self.settings = self.common.settings + self.common.log('TorConnectionDialog', '__init__') self.qtapp = qtapp - self.settings = settings self.onion = onion self.setWindowTitle("OnionShare") @@ -60,7 +64,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def start(self): self.common.log('TorConnectionDialog', 'start') - t = TorConnectionThread(self.common, self, self.settings, self.onion) + t = TorConnectionThread(self.common, self.settings, self, self.onion) t.tor_status_update.connect(self._tor_status_update) t.connected_to_tor.connect(self._connected_to_tor) t.canceled_connecting_to_tor.connect(self._canceled_connecting_to_tor) @@ -116,15 +120,16 @@ class TorConnectionThread(QtCore.QThread): canceled_connecting_to_tor = QtCore.pyqtSignal() error_connecting_to_tor = QtCore.pyqtSignal(str) - def __init__(self, common, dialog, settings, onion): + def __init__(self, common, settings, dialog, onion): super(TorConnectionThread, self).__init__() self.common = common self.common.log('TorConnectionThread', '__init__') - self.dialog = dialog self.settings = settings + + self.dialog = dialog self.onion = onion def run(self): -- cgit v1.2.3-54-g00ecf From 8e82c07039cf678749d58886a4c0004224ddfcc0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 13 Mar 2018 03:59:52 -0700 Subject: Fixed bug in validating downloads dir related to moving settings into common --- onionshare/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 5b23ad3e..e3a17538 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -98,7 +98,7 @@ def main(cwd=None): except: print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir'))) valid = False - if valid and not os.access(settings.get('downloads_dir'), os.W_OK): + if valid and not os.access(common.settings.get('downloads_dir'), os.W_OK): print(strings._('error_downloads_dir_not_writable').format(common.settings.get('downloads_dir'))) valid = False if not valid: -- cgit v1.2.3-54-g00ecf From 000d9620c19344265120f7cc155109cfc9641c56 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 13 Mar 2018 05:50:26 -0700 Subject: Add flash messages to receive template, and begin implementing upload POST --- onionshare/web.py | 24 +++++++++++++++++++++--- share/static/css/style.css | 12 ++++++++++++ share/templates/receive.html | 12 +++++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index b6739bcb..0027bf0f 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -31,9 +31,10 @@ from distutils.version import LooseVersion as Version from urllib.request import urlopen from flask import ( - Flask, Response, request, render_template, abort, make_response, - __version__ as flask_version + Flask, Response, request, render_template, abort, make_response, flash, + redirect, __version__ as flask_version ) +from werkzeug.utils import secure_filename from . import strings, common @@ -48,6 +49,7 @@ class Web(object): self.app = Flask(__name__, static_folder=common.get_resource_path('static'), template_folder=common.get_resource_path('templates')) + self.app.secret_key = self.common.random_string(8) # Debug mode? if self.common.debug: @@ -61,6 +63,8 @@ class Web(object): # Are we using receive mode? self.receive_mode = receive_mode + if self.receive_mode: + self.app.config['UPLOAD_FOLDER'] = self.common.settings.get('downloads_dir') # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in @@ -257,12 +261,26 @@ class Web(object): def index(slug_candidate): self.check_slug_candidate(slug_candidate) - # If download is allowed to continue, serve download page r = make_response(render_template( 'receive.html', slug=self.slug)) return self.add_security_headers(r) + @self.app.route("//upload", methods=['POST']) + def upload(slug_candidate): + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + self.check_slug_candidate(slug_candidate) + self.common.log('Web', 'upload, request.files: {}'.format(request.files)) + + # Check if the post request has the file part + if 'file' not in request.files: + flash('No files were selected to upload') + return redirect('/{}'.format(slug_candidate)) + + files = request.files['file'] + return '' + def common_routes(self): """ Common web app routes between sending and receiving diff --git a/share/static/css/style.css b/share/static/css/style.css index c3304f39..c65c11f7 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -115,3 +115,15 @@ table.file-list td:last-child { color: #666666; margin: 0 0 20px 0; } + +ul.flashes { + list-style: none; + margin: 0; + padding: 0; + color: #cc0000; +} + +ul.flashes li { + margin: 0; + padding: 10px; +} diff --git a/share/templates/receive.html b/share/templates/receive.html index 6ad3aebc..d7db31de 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -12,12 +12,22 @@

    OnionShare

    + {% with messages = get_flashed_messages() %} + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} + {% endwith %} +

    Send Files

    Select the files you want to send, then click "Send Files"...

    -
    +

    -- cgit v1.2.3-54-g00ecf From b1b28f4fa57c5df6cade9be60669237bff5223dc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 14 Mar 2018 18:31:07 +1100 Subject: Hide the primary_action when Tor gets disconnected --- onionshare_gui/onionshare_gui.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 7dd641dd..ef52a55a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -345,7 +345,8 @@ class OnionShareGui(QtWidgets.QMainWindow): # If there were some files listed for sharing, we should be ok to # re-enable the 'Start Sharing' button now. if self.server_status.file_selection.get_num_files() > 0: - self.server_status.server_button.setEnabled(True) + self.primary_action.show() + self.info_widget.show() self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.settings.get('shutdown_timeout'): @@ -554,7 +555,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.timer.stop() if self.server_status.status != self.server_status.STATUS_STOPPED: self.server_status.stop_server() - self.server_status.server_button.setEnabled(False) + self.primary_action.hide() + self.info_widget.hide() self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) -- cgit v1.2.3-54-g00ecf From 01dd16d92fb8329d60558bbb8985cf342fe7c4cd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 04:03:50 -0700 Subject: Uploading files works in CLI --- onionshare/web.py | 57 +++++++++++++++++++++++++++++++++++--------- share/templates/receive.html | 2 +- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 0027bf0f..be2d1a07 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -63,8 +63,6 @@ class Web(object): # Are we using receive mode? self.receive_mode = receive_mode - if self.receive_mode: - self.app.config['UPLOAD_FOLDER'] = self.common.settings.get('downloads_dir') # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in @@ -268,18 +266,55 @@ class Web(object): @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user self.check_slug_candidate(slug_candidate) - self.common.log('Web', 'upload, request.files: {}'.format(request.files)) - # Check if the post request has the file part - if 'file' not in request.files: - flash('No files were selected to upload') - return redirect('/{}'.format(slug_candidate)) + files = request.files.getlist('file[]') + filenames = [] + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded') + else: + flash('Uploaded {}'.format(', '.join(filenames))) - files = request.files['file'] - return '' + return redirect('/{}'.format(slug_candidate)) def common_routes(self): """ diff --git a/share/templates/receive.html b/share/templates/receive.html index d7db31de..1ced74da 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -28,7 +28,7 @@

    Send Files

    Select the files you want to send, then click "Send Files"...

    -

    +

    -- cgit v1.2.3-54-g00ecf From 0b10e71547f80a2f8da119534f7bb1f2fcf37ba7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 07:35:04 -0700 Subject: Add receive mode warning, and print notification for each upload, in CLI mode --- onionshare/__init__.py | 3 +++ onionshare/web.py | 3 +++ share/locale/en.json | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index e3a17538..0d5156d8 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -162,6 +162,9 @@ def main(cwd=None): print('') if receive: + print(strings._('receive_mode_warning')) + print('') + if stealth: print(strings._("give_this_url_receive_stealth")) print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) diff --git a/onionshare/web.py b/onionshare/web.py index be2d1a07..495e1364 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -305,6 +305,9 @@ class Web(object): valid = True self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print('') + print(strings._('receive_mode_received_file').format(local_path)) + print('') f.save(local_path) # Note that flash strings are on English, and not translated, on purpose, diff --git a/share/locale/en.json b/share/locale/en.json index 3340afd4..214aa32f 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -158,5 +158,7 @@ "info_in_progress_downloads_tooltip": "{} download(s) in progress", "info_completed_downloads_tooltip": "{} download(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", - "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}" + "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", + "receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", + "receive_mode_received_file": "Received file: {}" } -- cgit v1.2.3-54-g00ecf From 01f86daf8f047095aab9c35fb913d55161863301 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 08:16:09 -0700 Subject: In receive mode, allow uploader to close the server when they are done --- onionshare/web.py | 8 ++++++-- share/static/css/style.css | 14 ++++++++++++++ share/templates/receive.html | 27 ++++++++++++++++----------- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 495e1364..f0c76db7 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -305,9 +305,7 @@ class Web(object): valid = True self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print('') print(strings._('receive_mode_received_file').format(local_path)) - print('') f.save(local_path) # Note that flash strings are on English, and not translated, on purpose, @@ -319,6 +317,12 @@ class Web(object): return redirect('/{}'.format(slug_candidate)) + @self.app.route("//close", methods=['POST']) + def close(slug_candidate): + self.check_slug_candidate(slug_candidate) + self.force_shutdown() + return "" + def common_routes(self): """ Common web app routes between sending and receiving diff --git a/share/static/css/style.css b/share/static/css/style.css index c65c11f7..6b372f22 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -56,6 +56,20 @@ header .right ul li { cursor: pointer; } +.close-button { + color: #ffffff; + background-color: #c90c0c; + padding: 10px; + border: 0; + border-radius: 5px; + text-decoration: none; + margin-left: 1rem; + cursor: pointer; + position: absolute; + right: 10px; + bottom: 10px; +} + table.file-list { width: 100%; margin: 0 auto; diff --git a/share/templates/receive.html b/share/templates/receive.html index 1ced74da..4bcf27ad 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -12,16 +12,6 @@

    OnionShare

    - {% with messages = get_flashed_messages() %} - {% if messages %} -
      - {% for message in messages %} -
    • {{ message }}
    • - {% endfor %} -
    - {% endif %} - {% endwith %} -

    @@ -29,10 +19,25 @@

    Select the files you want to send, then click "Send Files"...

    -

    +

    +
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} + {% endwith %} +
    +
    + +
    + -- cgit v1.2.3-54-g00ecf From 72698a7247b0379457ae1521cf9df3353dc7ad73 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 08:30:14 -0700 Subject: Display a template after closing the server, and standardize the style of other simple templates --- onionshare/web.py | 3 ++- share/static/css/404.css | 6 ------ share/static/css/denied.css | 7 ------- share/templates/404.html | 7 ++++--- share/templates/closed.html | 10 ++++++++++ share/templates/denied.html | 1 - share/templates/receive.html | 4 ++-- 7 files changed, 18 insertions(+), 20 deletions(-) delete mode 100644 share/static/css/404.css delete mode 100644 share/static/css/denied.css create mode 100644 share/templates/closed.html diff --git a/onionshare/web.py b/onionshare/web.py index f0c76db7..4a6b8c71 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -321,7 +321,8 @@ class Web(object): def close(slug_candidate): self.check_slug_candidate(slug_candidate) self.force_shutdown() - return "" + r = make_response(render_template('closed.html')) + return self.add_security_headers(r) def common_routes(self): """ diff --git a/share/static/css/404.css b/share/static/css/404.css deleted file mode 100644 index e8f377ca..00000000 --- a/share/static/css/404.css +++ /dev/null @@ -1,6 +0,0 @@ -body { - background-color: #FFC4D5; - color: #FF0048; - text-align: center; - font-size: 20em; -} diff --git a/share/static/css/denied.css b/share/static/css/denied.css deleted file mode 100644 index 260a3dab..00000000 --- a/share/static/css/denied.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background-color: #222222; - color: #ffffff; - text-align: center; - font-family: sans-serif; - padding: 5em 1em; -} diff --git a/share/templates/404.html b/share/templates/404.html index 1cf9d7a2..10a4c8ef 100644 --- a/share/templates/404.html +++ b/share/templates/404.html @@ -1,9 +1,10 @@ - Error 404 + OnionsShare: Error 404 - - 404 + +

    Error 404: You probably typed the OnionShare address wrong

    + diff --git a/share/templates/closed.html b/share/templates/closed.html new file mode 100644 index 00000000..167d0efc --- /dev/null +++ b/share/templates/closed.html @@ -0,0 +1,10 @@ + + + + OnionShare is closed + + + +

    Thank you for using OnionShare

    + + diff --git a/share/templates/denied.html b/share/templates/denied.html index 39974639..5d411d62 100644 --- a/share/templates/denied.html +++ b/share/templates/denied.html @@ -3,7 +3,6 @@ OnionShare -

    OnionShare download in progress

    diff --git a/share/templates/receive.html b/share/templates/receive.html index 4bcf27ad..d1ec3b3a 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -19,7 +19,7 @@

    Select the files you want to send, then click "Send Files"...

    -

    +

    {% with messages = get_flashed_messages() %} {% if messages %} @@ -36,7 +36,7 @@
    - +
    -- cgit v1.2.3-54-g00ecf From fee1d495634c302c065632cfe56cba5f05774434 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 08:33:25 -0700 Subject: Fix bug with shutdown_slug --- onionshare/web.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 4a6b8c71..ca0bd044 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -350,7 +350,7 @@ class Web(object): """ Stop the flask web server, from the context of an http request. """ - self.check_slug_candidate(slug_candidate, shutdown_slug) + self.check_slug_candidate(slug_candidate, self.shutdown_slug) self.force_shutdown() return "" @@ -472,10 +472,10 @@ class Web(object): try: s = socket.socket() s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(shutdown_slug)) + s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) except: try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, shutdown_slug)).read() + urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() except: pass -- cgit v1.2.3-54-g00ecf From 929ad58ebdc527ba0ef4ec3ddb834742f70ecc9b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 14 Mar 2018 08:34:43 -0700 Subject: Fix bug with validating filenames from args in GUI --- onionshare_gui/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index e1ad8743..13612e77 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -92,10 +92,10 @@ def main(): valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): - Alert(self.common, strings._("not_a_file", True).format(filename)) + Alert(common, strings._("not_a_file", True).format(filename)) valid = False if not os.access(filename, os.R_OK): - Alert(self.common, strings._("not_a_readable_file", True).format(filename)) + Alert(common, strings._("not_a_readable_file", True).format(filename)) valid = False if not valid: sys.exit() -- cgit v1.2.3-54-g00ecf From ebd0416f1d3d6bee5205e0b84f8459b68d239a39 Mon Sep 17 00:00:00 2001 From: irykoon Date: Wed, 14 Mar 2018 16:16:56 +0000 Subject: Support meek_lite Pluggable Transports on Windows and Mac According to https://lists.torproject.org/pipermail/tor-announce/2018-March/000153.html: Tor Browser 7.5.1 includes obfs4proxy (0.0.7), which supports meek_lite Pluggable Transports. Therefore, undo: https://github.com/micahflee/onionshare/pull/649 --- CHANGELOG.md | 4 ++++ onionshare_gui/settings_dialog.py | 11 ++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a03a151..33b94bcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # OnionShare Changelog +## 1.4 +* New feature: Support for meek_lite pluggable transports (Amazon and Azure) on Windows and Mac +* New feature: Support for custom obfs4 and meek_lite bridges on Windows and Mac + ## 1.3 * Major UI redesign, introducing many UX improvements diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7c81afc6..fe45c2a2 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -184,11 +184,6 @@ class SettingsDialog(QtWidgets.QDialog): 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.toggled.connect(self.tor_bridges_use_meek_lite_azure_radio_toggled) - # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser - if self.system == 'Windows' or self.system == 'Darwin': - self.tor_bridges_use_meek_lite_amazon_radio.hide() - self.tor_bridges_use_meek_lite_azure_radio.hide() - # 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.toggled.connect(self.tor_bridges_use_custom_radio_toggled) @@ -835,10 +830,8 @@ class SettingsDialog(QtWidgets.QDialog): ipv6_pattern = re.compile("(obfs4\s+)?\[(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]:[0-9]+\s+[A-Z0-9]+(.+)$") meek_lite_pattern = re.compile("(meek_lite)(\s)+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)(\s)+([0-9A-Z]+)(\s)+url=(.+)(\s)+front=(.+)") if ipv4_pattern.match(bridge) or \ - ipv6_pattern.match(bridge): - new_bridges.append(''.join(['Bridge ', bridge, '\n'])) - bridges_valid = True - if self.system != 'Windows' and self.system != 'Darwin' and meek_lite_pattern.match(bridge): + ipv6_pattern.match(bridge) or \ + meek_lite_pattern.match(bridge): new_bridges.append(''.join(['Bridge ', bridge, '\n'])) bridges_valid = True -- cgit v1.2.3-54-g00ecf From 98ee6e8d4e1a5d3fd3ee6518f69b72217de1ed31 Mon Sep 17 00:00:00 2001 From: irykoon Date: Thu, 15 Mar 2018 03:53:02 +0000 Subject: Remove advanced CHANGELOG Thanks to @mig5 for bringing this issue up. Please feel free to add the changelog back when the time is appropriate. --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b94bcc..4a03a151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # OnionShare Changelog -## 1.4 -* New feature: Support for meek_lite pluggable transports (Amazon and Azure) on Windows and Mac -* New feature: Support for custom obfs4 and meek_lite bridges on Windows and Mac - ## 1.3 * Major UI redesign, introducing many UX improvements -- cgit v1.2.3-54-g00ecf From aafa9b1543bf07c3714a681c698baa4377c55092 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 19 Mar 2018 02:25:22 -0700 Subject: Add WSGI middleware in order to capture the progress of POST request uploads --- onionshare/web.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/onionshare/web.py b/onionshare/web.py index ca0bd044..9f4c18d9 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -27,6 +27,7 @@ import socket import sys import tempfile import zipfile +import re from distutils.version import LooseVersion as Version from urllib.request import urlopen @@ -63,6 +64,9 @@ class Web(object): # Are we using receive mode? self.receive_mode = receive_mode + if self.receive_mode: + # In receive mode, use WSGI middleware to track the progess of upload POSTs + self.app.wsgi_app = UploadProgessMiddleware(self.app.wsgi_app, self) # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in @@ -528,3 +532,28 @@ class ZipWriter(object): Close the zip archive. """ self.z.close() + + +class UploadProgessMiddleware(object): + def __init__(self, app, web): + self.app = app + self.web = web + + self.upload_regex = re.compile('/(.*)/upload') + + def __call__(self, environ, start_response): + # Check if this is a POST request to /[slug]/upload + valid_upload_request = False + if environ.get('REQUEST_METHOD') == 'POST': + match = self.upload_regex.match(environ.get('PATH_INFO')) + if match: + slug_candidate = match.group(1) + if hmac.compare_digest(self.web.slug, slug_candidate): + valid_upload_request = True + + # If this is a valid upload request, stream the upload + if valid_upload_request: + #print(environ.get('wsgi.input')) + pass + + return self.app(environ, start_response) -- cgit v1.2.3-54-g00ecf From facd441caf15756a29e864b9e73e835f0d4c3184 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 21 Mar 2018 17:34:11 -0700 Subject: For receive mode, use a custom flask Request, and a custom TemporaryFile, in order to keep track of file upload progress --- onionshare/web.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 9f4c18d9..f7f02526 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -28,12 +28,13 @@ import sys import tempfile import zipfile import re +import io from distutils.version import LooseVersion as Version from urllib.request import urlopen from flask import ( - Flask, Response, request, render_template, abort, make_response, flash, - redirect, __version__ as flask_version + Flask, Response, Request, request, render_template, abort, make_response, + flash, redirect, __version__ as flask_version ) from werkzeug.utils import secure_filename @@ -66,7 +67,9 @@ class Web(object): self.receive_mode = receive_mode if self.receive_mode: # In receive mode, use WSGI middleware to track the progess of upload POSTs - self.app.wsgi_app = UploadProgessMiddleware(self.app.wsgi_app, self) + self.app.wsgi_app = UploadProgessMiddleware(self.common, self.app.wsgi_app, self) + # Use a custom Request class + self.app.request_class = ReceiveModeRequest # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in @@ -535,7 +538,8 @@ class ZipWriter(object): class UploadProgessMiddleware(object): - def __init__(self, app, web): + def __init__(self, common, app, web): + self.common = common self.app = app self.web = web @@ -553,7 +557,68 @@ class UploadProgessMiddleware(object): # If this is a valid upload request, stream the upload if valid_upload_request: - #print(environ.get('wsgi.input')) - pass + length = environ.get('CONTENT_LENGTH') + self.common.log('UploadProgessMiddleware', 'upload started, {} bytes'.format(length)) return self.app(environ, start_response) + + +class ReceiveModeTemporaryFile(object): + """ + A custom TemporaryFile that tells ReceiveModeRequest every time data gets + written to it, in order to track the progress of uploads. + """ + def __init__(self, filename, update_func): + self.onionshare_filename = filename + self.onionshare_update_func = update_func + + # Create a temporary file + self.f = tempfile.TemporaryFile('wb+') + + # Make all the file-like methods and attributes actually access the + # TemporaryFile, except for write + attrs = ['close', 'closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', + 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', + 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', + 'truncate', 'writable', 'writelines'] + for attr in attrs: + setattr(self, attr, getattr(self.f, attr)) + + def write(self, b): + """ + Custom write method that calls out to onionshare_update_func + """ + bytes_written = self.f.write(b) + self.onionshare_update_func(self.onionshare_filename, bytes_written) + + +class ReceiveModeRequest(Request): + """ + A custom flask Request object that keeps track of how much data has been + uploaded for each file, for receive mode. + """ + def __init__(self, environ, populate_request=True, shallow=False): + super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) + + # The total size of this request, which may include multiple files + self.total_content_length = 0 + + # A dictionary that maps filenames to the bytes uploaded so far + self.onionshare_progress = {} + + def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): + """ + This gets called for each file that gets uploaded, and returns an file-like + writable stream. + """ + print('') + self.total_content_length = total_content_length + self.onionshare_progress[filename] = 0 + return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) + + def onionshare_update_func(self, filename, length): + """ + Keep track of the bytes uploaded so far for all files. + """ + self.onionshare_progress[filename] += length + print('\r{}: {} '.format(filename, self.onionshare_progress[filename]), end='') -- cgit v1.2.3-54-g00ecf From f7640416eb8544201d7755a4968227e70c9c6134 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 21 Mar 2018 17:51:42 -0700 Subject: Remove the WSGI middleware, because I'm solving the problem in a different way --- onionshare/web.py | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index f7f02526..15a68cec 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -66,9 +66,7 @@ class Web(object): # Are we using receive mode? self.receive_mode = receive_mode if self.receive_mode: - # In receive mode, use WSGI middleware to track the progess of upload POSTs - self.app.wsgi_app = UploadProgessMiddleware(self.common, self.app.wsgi_app, self) - # Use a custom Request class + # Use a custom Request class to track upload progess self.app.request_class = ReceiveModeRequest # Starting in Flask 0.11, render_template_string autoescapes template variables @@ -537,32 +535,6 @@ class ZipWriter(object): self.z.close() -class UploadProgessMiddleware(object): - def __init__(self, common, app, web): - self.common = common - self.app = app - self.web = web - - self.upload_regex = re.compile('/(.*)/upload') - - def __call__(self, environ, start_response): - # Check if this is a POST request to /[slug]/upload - valid_upload_request = False - if environ.get('REQUEST_METHOD') == 'POST': - match = self.upload_regex.match(environ.get('PATH_INFO')) - if match: - slug_candidate = match.group(1) - if hmac.compare_digest(self.web.slug, slug_candidate): - valid_upload_request = True - - # If this is a valid upload request, stream the upload - if valid_upload_request: - length = environ.get('CONTENT_LENGTH') - self.common.log('UploadProgessMiddleware', 'upload started, {} bytes'.format(length)) - - return self.app(environ, start_response) - - class ReceiveModeTemporaryFile(object): """ A custom TemporaryFile that tells ReceiveModeRequest every time data gets @@ -600,9 +572,6 @@ class ReceiveModeRequest(Request): def __init__(self, environ, populate_request=True, shallow=False): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) - # The total size of this request, which may include multiple files - self.total_content_length = 0 - # A dictionary that maps filenames to the bytes uploaded so far self.onionshare_progress = {} @@ -612,7 +581,6 @@ class ReceiveModeRequest(Request): writable stream. """ print('') - self.total_content_length = total_content_length self.onionshare_progress[filename] = 0 return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) -- cgit v1.2.3-54-g00ecf From bd7305ab16aa6900371b0403fde9cb56c79009b3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 21 Mar 2018 18:27:42 -0700 Subject: Add new WSGI middleware just to attach the Web object to environ, and improve the UI of file upload progress --- onionshare/web.py | 32 +++++++++++++++++++++++++++++--- share/static/css/style.css | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 15a68cec..7a6a848b 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -66,6 +66,8 @@ class Web(object): # Are we using receive mode? self.receive_mode = receive_mode if self.receive_mode: + # Use custom WSGI middleware, to modify environ + self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) # Use a custom Request class to track upload progess self.app.request_class = ReceiveModeRequest @@ -318,7 +320,8 @@ class Web(object): if len(filenames) == 0: flash('No files uploaded') else: - flash('Uploaded {}'.format(', '.join(filenames))) + for filename in filenames: + flash('Uploaded {}'.format(filename)) return redirect('/{}'.format(slug_candidate)) @@ -535,6 +538,19 @@ class ZipWriter(object): self.z.close() +class ReceiveModeWSGIMiddleware(object): + """ + Custom WSGI middleware in order to attach the Web object to environ, so + ReceiveModeRequest can access it. + """ + def __init__(self, app, web): + self.app = app + self.web = web + + def __call__(self, environ, start_response): + environ['web'] = self.web + return self.app(environ, start_response) + class ReceiveModeTemporaryFile(object): """ A custom TemporaryFile that tells ReceiveModeRequest every time data gets @@ -571,6 +587,7 @@ class ReceiveModeRequest(Request): """ def __init__(self, environ, populate_request=True, shallow=False): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) + self.web = environ['web'] # A dictionary that maps filenames to the bytes uploaded so far self.onionshare_progress = {} @@ -580,13 +597,22 @@ class ReceiveModeRequest(Request): This gets called for each file that gets uploaded, and returns an file-like writable stream. """ - print('') + if len(self.onionshare_progress) > 0: + print('') self.onionshare_progress[filename] = 0 return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) + def close(self): + """ + When closing the request, print a newline if this was a file upload. + """ + super(ReceiveModeRequest, self).close() + if len(self.onionshare_progress) > 0: + print('') + def onionshare_update_func(self, filename, length): """ Keep track of the bytes uploaded so far for all files. """ self.onionshare_progress[filename] += length - print('\r{}: {} '.format(filename, self.onionshare_progress[filename]), end='') + print('{} - {} '.format(self.web.common.human_readable_filesize(self.onionshare_progress[filename]), filename), end='\r') diff --git a/share/static/css/style.css b/share/static/css/style.css index 6b372f22..29b839a7 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -135,6 +135,7 @@ ul.flashes { margin: 0; padding: 0; color: #cc0000; + text-align: left; } ul.flashes li { -- cgit v1.2.3-54-g00ecf From 3a5133ee7a1cf5a3ed6a3e11b0d9338737ac14d0 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Thu, 22 Mar 2018 22:06:08 +0100 Subject: marked nouns, ellipsis, +, set up, addresses, could not --- share/locale/en.json | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index fe5f030a..b99a153f 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -2,12 +2,12 @@ "config_onion_service": "Configuring onion service on port {0:d}.", "preparing_files": "Preparing files to share.", "wait_for_hs": "Waiting for HS to be ready:", - "wait_for_hs_trying": "Trying...", + "wait_for_hs_trying": "Trying…", "wait_for_hs_nope": "Not ready yet.", "wait_for_hs_yup": "Ready!", "give_this_url": "Give this address to the person you're sending the file to:", "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Press Ctrl-C to stop server", + "ctrlc_to_stop": "Press Ctrl+C to stop the server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", "no_available_port": "Could not start the Onion service as there was no available port.", @@ -17,7 +17,7 @@ "closing_automatically": "Stopped because download finished", "timeout_download_still_running": "Waiting for download to complete", "large_filesize": "Warning: Sending large files could take hours", - "error_tails_invalid_port": "Invalid value, port must be an integer", + "error_tails_invalid_port": "Invalid value, port must be a regular number", "error_tails_unknown_root": "Unknown error with Tails root process", "systray_menu_exit": "Quit", "systray_download_started_title": "OnionShare Download Started", @@ -26,10 +26,10 @@ "systray_download_completed_message": "The user finished downloading your files", "systray_download_canceled_title": "OnionShare Download Canceled", "systray_download_canceled_message": "The user canceled the download", - "help_local_only": "Do not attempt to use tor: for development only", + "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", - "help_transparent_torification": "My system is transparently torified", + "help_transparent_torification": "My system is transparently Torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Log application errors to stdout, and log web errors to disk", "help_filename": "List of files or folders to share", @@ -50,9 +50,9 @@ "gui_copied_url": "The OnionShare address has been copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", - "gui_starting_server1": "Starting Tor onion service...", - "gui_starting_server2": "Compressing files...", - "gui_please_wait": "Starting... Click to cancel", + "gui_starting_server1": "Starting Tor onion service…", + "gui_starting_server2": "Compressing files…", + "gui_please_wait": "Starting… Click to cancel", "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", @@ -71,18 +71,18 @@ "gui_settings_window_title": "Settings", "gui_settings_stealth_label": "Stealth (advanced)", "gui_settings_stealth_option": "Create stealth onion services", - "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to it.
    More information.", + "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.
    More information.", "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.", - "gui_settings_autoupdate_label": "Check for updates", - "gui_settings_autoupdate_option": "Notify me when updates are available", + "gui_settings_autoupdate_label": "Check for upgrades", + "gui_settings_autoupdate_option": "Notify me when upgrades are available", "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", - "gui_settings_autoupdate_check_button": "Check For Updates", + "gui_settings_autoupdate_check_button": "Check For Upgrades", "gui_settings_sharing_label": "Sharing options", "gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_systray_notifications": "Show desktop notifications", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", - "gui_settings_connection_type_bundled_option": "Use Tor that is bundled with OnionShare", + "gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare", "gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser", "gui_settings_connection_type_control_port_option": "Connect using control port", "gui_settings_connection_type_socket_file_option": "Connect using socket file", @@ -106,7 +106,7 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to be valid.\nPlease try again with valid bridges.", + "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem work.\nPlease try again by double-checking them or adding other ones.", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", @@ -119,15 +119,15 @@ "settings_error_socket_file": "Can't connect to Tor controller using socket file {}.", "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", - "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.", - "settings_error_bundled_tor_not_supported": "Bundled Tor is not supported when not using developer mode in Windows or macOS.", - "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your clock isn't accurate.", + "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", + "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when not using developer mode on Windows or macOS.", + "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", - "settings_error_bundled_tor_broken": "Something is wrong with OnionShare connecting to Tor in the background:\n{}", + "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", - "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", "connecting_to_tor": "Connecting to the Tor network", - "update_available": "There is an OnionShare update available. Click here to download it.

    Installed version: {}
    Latest version: {}", + "update_available": "A new version of OnionShare is available. Click here to download it.

    Installed version: {}
    Latest version: {}", "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.", "update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.", "update_not_available": "You are running the latest version of OnionShare.", @@ -135,19 +135,19 @@ "gui_tor_connection_ask_open_settings": "Open Settings", "gui_tor_connection_ask_quit": "Quit", "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.", - "gui_tor_connection_canceled": "OnionShare cannot connect to Tor.\n\nMake sure you're connected to the internet, then re-open OnionShare to configure the Tor connection.", + "gui_tor_connection_canceled": "OnionShare could not connect to Tor.\n\nMake sure you're connected to the Internet, then re-open OnionShare to set up the Tor connection.", "gui_tor_connection_lost": "Disconnected from Tor.", "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", - "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved address)", - "gui_url_description": "Anyone with this link can download your files using Tor Browser: ", - "gui_url_label_persistent": "This share will not stop automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", + "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", + "gui_url_description": "Anyone with this link can download your files using the Tor Browser: ", + "gui_url_label_persistent": "This share will not stop automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", "gui_url_label_stay_open": "This share will not stop automatically unless a timer is set.", "gui_url_label_onetime": "This share will stop after the first download", "gui_url_label_onetime_and_persistent": "This share will stop after the first download

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", "gui_status_indicator_stopped": "Ready to Share", - "gui_status_indicator_working": "Starting...", + "gui_status_indicator_working": "Starting…", "gui_status_indicator_started": "Sharing", "gui_file_info": "{} Files, {}", "gui_file_info_single": "{} File, {}", -- cgit v1.2.3-54-g00ecf From f35061fa10876754d1cd852fe17012a14ef5d7dd Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Thu, 22 Mar 2018 22:24:16 +0100 Subject: seem work → seem to work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index b99a153f..377633fe 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -106,7 +106,7 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem work.\nPlease try again by double-checking them or adding other ones.", + "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to work.\nPlease try again by double-checking them or adding other ones.", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", -- cgit v1.2.3-54-g00ecf From 16eab5e85013bd273f4f9a4d9011e9822c4c1b25 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Apr 2018 15:25:28 -0700 Subject: Add a stretch at the bottom of the downloads window, so that progess bars are always lined up at the top --- onionshare_gui/downloads.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 82b2366a..5f82e8ba 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -114,9 +114,13 @@ class Downloads(QtWidgets.QWidget): self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + self.downloads_layout = QtWidgets.QVBoxLayout() + self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.downloads_label) self.layout.addWidget(self.no_downloads_label) + self.layout.addLayout(self.downloads_layout) + self.layout.addStretch() self.setLayout(self.layout) def add_download(self, download_id, total_bytes): @@ -126,7 +130,7 @@ class Downloads(QtWidgets.QWidget): # add it to the list download = Download(download_id, total_bytes) self.downloads[download_id] = download - self.layout.addWidget(download.progress_bar) + self.downloads_layout.addWidget(download.progress_bar) def update_download(self, download_id, downloaded_bytes): """ @@ -145,6 +149,6 @@ class Downloads(QtWidgets.QWidget): Reset the downloads back to zero """ for download in self.downloads.values(): - self.layout.removeWidget(download.progress_bar) + self.downloads_layout.removeWidget(download.progress_bar) download.progress_bar.close() self.downloads = {} -- cgit v1.2.3-54-g00ecf From 87d29a9171a9ee31490aa04181a6e9a40a2132de Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Apr 2018 17:15:15 -0700 Subject: Rearrange some widgets in the settings dialog, specifically move the bridge options above the test Tor settings button --- onionshare_gui/settings_dialog.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index f60107af..c9009776 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -309,16 +309,6 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_radio_group = QtWidgets.QGroupBox(strings._("gui_settings_connection_type_label", True)) connection_type_radio_group.setLayout(connection_type_radio_group_layout) - # Connection type layout - connection_type_group_layout = QtWidgets.QVBoxLayout() - connection_type_group_layout.addWidget(self.connection_type_control_port_extras) - connection_type_group_layout.addWidget(self.connection_type_socket_file_extras) - connection_type_group_layout.addWidget(self.connection_type_socks) - connection_type_group_layout.addWidget(self.authenticate_group) - connection_type_group_layout.addWidget(self.connection_type_test_button) - connection_type_group = QtWidgets.QGroupBox() - connection_type_group.setLayout(connection_type_group_layout) - # 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.addWidget(self.bridges) @@ -326,6 +316,15 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() + # Connection type layout + connection_type_layout = QtWidgets.QVBoxLayout() + connection_type_layout.addWidget(self.connection_type_control_port_extras) + connection_type_layout.addWidget(self.connection_type_socket_file_extras) + connection_type_layout.addWidget(self.connection_type_socks) + connection_type_layout.addWidget(self.authenticate_group) + connection_type_layout.addWidget(self.connection_type_bridges_radio_group) + connection_type_layout.addWidget(self.connection_type_test_button) + # Buttons self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) self.save_button.clicked.connect(self.save_clicked) @@ -356,8 +355,7 @@ class SettingsDialog(QtWidgets.QDialog): right_col_layout = QtWidgets.QVBoxLayout() right_col_layout.addWidget(connection_type_radio_group) - right_col_layout.addWidget(connection_type_group) - right_col_layout.addWidget(self.connection_type_bridges_radio_group) + right_col_layout.addLayout(connection_type_layout) right_col_layout.addWidget(self.tor_status) right_col_layout.addStretch() -- cgit v1.2.3-54-g00ecf From a53d3188dd811b7451b1c343ccd53d4114c79a9b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Apr 2018 17:20:58 -0700 Subject: Put test Tor button in a so it can be left-aligned --- onionshare_gui/settings_dialog.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index c9009776..c70f5695 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -296,10 +296,6 @@ class SettingsDialog(QtWidgets.QDialog): self.authenticate_group = QtWidgets.QGroupBox(strings._("gui_settings_authenticate_label", True)) self.authenticate_group.setLayout(authenticate_group_layout) - # Test tor settings button - self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) - self.connection_type_test_button.clicked.connect(self.test_tor_clicked) - # Put the radios into their own group so they are exclusive connection_type_radio_group_layout = QtWidgets.QVBoxLayout() connection_type_radio_group_layout.addWidget(self.connection_type_bundled_radio) @@ -316,6 +312,13 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_bridges_radio_group.setLayout(connection_type_bridges_radio_group_layout) self.connection_type_bridges_radio_group.hide() + # Test tor settings button + self.connection_type_test_button = QtWidgets.QPushButton(strings._('gui_settings_connection_type_test_button', True)) + self.connection_type_test_button.clicked.connect(self.test_tor_clicked) + connection_type_test_button_layout = QtWidgets.QHBoxLayout() + connection_type_test_button_layout.addWidget(self.connection_type_test_button) + connection_type_test_button_layout.addStretch() + # Connection type layout connection_type_layout = QtWidgets.QVBoxLayout() connection_type_layout.addWidget(self.connection_type_control_port_extras) @@ -323,7 +326,7 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addWidget(self.connection_type_socks) connection_type_layout.addWidget(self.authenticate_group) connection_type_layout.addWidget(self.connection_type_bridges_radio_group) - connection_type_layout.addWidget(self.connection_type_test_button) + connection_type_layout.addLayout(connection_type_test_button_layout) # Buttons self.save_button = QtWidgets.QPushButton(strings._('gui_settings_button_save', True)) -- cgit v1.2.3-54-g00ecf From 91536ea571f30da3e2d29dd14e2e00cd61a121a2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 22 Apr 2018 17:46:14 -0700 Subject: Fix a few bugs that I missed when merging in develop --- onionshare/onion.py | 4 ++-- onionshare_gui/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 8 ++++---- onionshare_gui/settings_dialog.py | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 57e407ea..dc47019f 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -446,11 +446,11 @@ class Onion(object): if self.settings.get('private_key'): key_type = "RSA1024" key_content = self.settings.get('private_key') - self.common.log('Onion', 'Starting a hidden service with a saved private key') + self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a saved private key') else: key_type = "NEW" key_content = "RSA1024" - self.common.log('Onion', 'Starting a hidden service with a new private key') + self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a new private key') try: if basic_auth != None: diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 39269b07..11a5999c 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -110,7 +110,7 @@ def main(): app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) # Launch the gui - gui = OnionShareGui(onion, qtapp, app, filenames, config, local_only) + gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config, local_only) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index b5e411f2..d5a0889a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -107,7 +107,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.filesize_warning.hide() # Downloads - self.downloads = Downloads() + self.downloads = Downloads(self.common) self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -118,7 +118,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') self.info_show_downloads = QtWidgets.QToolButton() - self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) self.info_show_downloads.setCheckable(True) self.info_show_downloads.toggled.connect(self.downloads_toggled) self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) @@ -655,7 +655,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ When the 'Show/hide downloads' button is toggled, show or hide the downloads window. """ - common.log('OnionShareGui', 'toggle_downloads') + self.common.log('OnionShareGui', 'toggle_downloads') if checked: self.downloads.downloads_container.show() else: @@ -701,7 +701,7 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.update_downloads_completed(0) self.update_downloads_in_progress(0) - self.info_show_downloads.setIcon(QtGui.QIcon(common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) self.downloads.no_downloads_label.show() self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index e7449b51..f38f996e 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -683,7 +683,7 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = False if not self.local_only: if self.onion.is_authenticated(): - common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') def changed(s1, s2, keys): """ Compare the Settings objects s1 and s2 and return true if any values @@ -705,20 +705,20 @@ class SettingsDialog(QtWidgets.QDialog): reboot_onion = True else: - common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') + self.common.log('SettingsDialog', 'save_clicked', 'Not connected to Tor') # Tor isn't connected, so try connecting reboot_onion = True # Do we need to reinitialize Tor? if reboot_onion: # Reinitialize the Onion object - common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') + self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) tor_con.start() - common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) + self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) if self.onion.is_authenticated() and not tor_con.wasCanceled(): self.settings_saved.emit() -- cgit v1.2.3-54-g00ecf From 97756ba8bcc19180acda7feb6a6903f92b8f40ac Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Mon, 23 Apr 2018 04:21:37 +0200 Subject: Spellign: - not, expire --- share/locale/en.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index 377633fe..9c16a272 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -29,7 +29,6 @@ "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", - "help_transparent_torification": "My system is transparently Torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Log application errors to stdout, and log web errors to disk", "help_filename": "List of files or folders to share", @@ -41,7 +40,7 @@ "gui_start_server": "Start Sharing", "gui_stop_server": "Stop Sharing", "gui_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", - "gui_stop_server_shutdown_timeout_tooltip": "Share will stop automatically at {}", + "gui_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", "gui_downloads": "Downloads:", @@ -120,7 +119,7 @@ "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", - "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when not using developer mode on Windows or macOS.", + "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", @@ -142,10 +141,10 @@ "share_via_onionshare": "Share via OnionShare", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", "gui_url_description": "Anyone with this link can download your files using the Tor Browser: ", - "gui_url_label_persistent": "This share will not stop automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", - "gui_url_label_stay_open": "This share will not stop automatically unless a timer is set.", - "gui_url_label_onetime": "This share will stop after the first download", - "gui_url_label_onetime_and_persistent": "This share will stop after the first download

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", + "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", + "gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.", + "gui_url_label_onetime": "This share will expire after the first download", + "gui_url_label_onetime_and_persistent": "This share will expire after the first download

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", "gui_status_indicator_stopped": "Ready to Share", "gui_status_indicator_working": "Starting…", "gui_status_indicator_started": "Sharing", -- cgit v1.2.3-54-g00ecf From 6c91d8977ac607a42f6245d62a8ad74c3d336418 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 19:51:51 -0700 Subject: Begin to add the mode switcher (between "Share Files" and "Receive Files", with the settings button) --- onionshare_gui/onionshare_gui.py | 62 +++++++++++++++++++++++++++++++++------ share/images/settings.png | Bin 411 -> 443 bytes share/locale/en.json | 4 ++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index d5a0889a..b2327b45 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -46,6 +46,9 @@ class OnionShareGui(QtWidgets.QMainWindow): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) + MODE_SHARE = 'share' + MODE_RECEIVE = 'receive' + def __init__(self, common, web, onion, qtapp, app, filenames, config=False, local_only=False): super(OnionShareGui, self).__init__() @@ -60,6 +63,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.app = app self.local_only = local_only + self.mode = self.MODE_SHARE + self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setMinimumWidth(430) @@ -68,6 +73,43 @@ class OnionShareGui(QtWidgets.QMainWindow): self.config = config self.common.load_settings(self.config) + # Mode switcher, to switch between share files and receive files + self.mode_switcher_selected_style = """ + QPushButton { + color: #ffffff; + background-color: #4e064f; + border: 0; + border-right: 1px solid #69266b; + font-weight: bold; + border-radius: 0; + }""" + self.mode_switcher_unselected_style = """ + QPushButton { + color: #ffffff; + background-color: #601f61; + border: 0; + border-right: 1px solid #69266b; + font-weight: normal; + border-radius: 0; + }""" + self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True)); + self.share_mode_button.setFixedHeight(50) + self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button', True)); + self.receive_mode_button.setFixedHeight(50) + self.settings_button = QtWidgets.QPushButton() + self.settings_button.setDefault(False) + self.settings_button.setFixedWidth(40) + self.settings_button.setFixedHeight(50) + self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) + self.settings_button.clicked.connect(self.open_settings) + self.settings_button.setStyleSheet('QPushButton { background-color: #601f61; border: 0; border-radius: 0; }') + mode_switcher_layout = QtWidgets.QHBoxLayout(); + mode_switcher_layout.setSpacing(0) + mode_switcher_layout.addWidget(self.share_mode_button) + mode_switcher_layout.addWidget(self.receive_mode_button) + mode_switcher_layout.addWidget(self.settings_button) + self.update_mode_switcher() + # File selection self.file_selection = FileSelection(self.common) if filenames: @@ -142,14 +184,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.info_widget.setLayout(self.info_layout) self.info_widget.hide() - # Settings button on the status bar - self.settings_button = QtWidgets.QPushButton() - self.settings_button.setDefault(False) - self.settings_button.setFlat(True) - self.settings_button.setFixedWidth(40) - self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) - self.settings_button.clicked.connect(self.open_settings) - # Server status indicator on the status bar self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) @@ -180,7 +214,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.setStyleSheet(statusBar_cssStyleData) self.status_bar.addPermanentWidget(self.server_status_indicator) - self.status_bar.addPermanentWidget(self.settings_button) self.setStatusBar(self.status_bar) # Status bar, zip progress bar @@ -201,6 +234,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Main layout self.layout = QtWidgets.QVBoxLayout() + self.layout.addLayout(mode_switcher_layout) self.layout.addWidget(self.info_widget) self.layout.addLayout(self.file_selection) self.layout.addWidget(self.primary_action) @@ -232,6 +266,16 @@ class OnionShareGui(QtWidgets.QMainWindow): # After connecting to Tor, check for updates self.check_for_updates() + def update_mode_switcher(self): + # Based on the current mode, switch the mode switcher button styles, + # and show and hide widgets to switch modes + if self.mode == self.MODE_SHARE: + self.share_mode_button.setStyleSheet(self.mode_switcher_selected_style) + self.receive_mode_button.setStyleSheet(self.mode_switcher_unselected_style) + else: + self.share_mode_button.setStyleSheet(self.mode_switcher_unselected_style) + self.receive_mode_button.setStyleSheet(self.mode_switcher_selected_style) + def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() diff --git a/share/images/settings.png b/share/images/settings.png index 4c69de07..ec35400a 100644 Binary files a/share/images/settings.png and b/share/images/settings.png differ diff --git a/share/locale/en.json b/share/locale/en.json index 6a983f9c..2c2a824e 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -163,5 +163,7 @@ "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", - "receive_mode_received_file": "Received file: {}" + "receive_mode_received_file": "Received file: {}", + "gui_mode_share_button": "Share Files", + "gui_mode_receive_button": "Receive Files" } -- cgit v1.2.3-54-g00ecf From b4c31573bad326b1590b731270fc53630d6f04fc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:08:03 -0700 Subject: Remove the margin from the mode switcher --- onionshare_gui/onionshare_gui.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index b2327b45..1648a471 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -232,14 +232,20 @@ class OnionShareGui(QtWidgets.QMainWindow): self.primary_action.hide() self.update_primary_action() - # Main layout - self.layout = QtWidgets.QVBoxLayout() - self.layout.addLayout(mode_switcher_layout) - self.layout.addWidget(self.info_widget) - self.layout.addLayout(self.file_selection) - self.layout.addWidget(self.primary_action) + # Layouts + contents_layout = QtWidgets.QVBoxLayout() + contents_layout.setContentsMargins(10, 10, 10, 10) + contents_layout.addWidget(self.info_widget) + contents_layout.addLayout(self.file_selection) + contents_layout.addWidget(self.primary_action) + + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(mode_switcher_layout) + layout.addLayout(contents_layout) + central_widget = QtWidgets.QWidget() - central_widget.setLayout(self.layout) + central_widget.setLayout(layout) self.setCentralWidget(central_widget) self.show() -- cgit v1.2.3-54-g00ecf From ac13790673fa7cb89eca5cf818a338e57ad37d81 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:15:30 -0700 Subject: Flip between modes when clicking mode buttons, and some css --- onionshare_gui/onionshare_gui.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 1648a471..25656c69 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -88,21 +88,28 @@ class OnionShareGui(QtWidgets.QMainWindow): color: #ffffff; background-color: #601f61; border: 0; - border-right: 1px solid #69266b; font-weight: normal; border-radius: 0; }""" self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True)); self.share_mode_button.setFixedHeight(50) + 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.setFixedHeight(50) + self.receive_mode_button.clicked.connect(self.receive_mode_clicked) self.settings_button = QtWidgets.QPushButton() self.settings_button.setDefault(False) self.settings_button.setFixedWidth(40) self.settings_button.setFixedHeight(50) self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) - self.settings_button.setStyleSheet('QPushButton { background-color: #601f61; border: 0; border-radius: 0; }') + self.settings_button.setStyleSheet(""" + QPushButton { + background-color: #601f61; + border: 0; + border-left: 1px solid #69266b; + border-radius: 0; + }""") mode_switcher_layout = QtWidgets.QHBoxLayout(); mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) @@ -202,17 +209,15 @@ class OnionShareGui(QtWidgets.QMainWindow): # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) - statusBar_cssStyleData =""" - QStatusBar { - font-style: italic; - color: #666666; - } - - QStatusBar::item { - border: 0px; - }""" - - self.status_bar.setStyleSheet(statusBar_cssStyleData) + self.status_bar.setStyleSheet(""" + QStatusBar { + font-style: italic; + color: #666666; + } + + QStatusBar::item { + border: 0px; + }""") self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) @@ -282,6 +287,14 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode_button.setStyleSheet(self.mode_switcher_unselected_style) self.receive_mode_button.setStyleSheet(self.mode_switcher_selected_style) + def share_mode_clicked(self): + self.mode = self.MODE_SHARE + self.update_mode_switcher() + + def receive_mode_clicked(self): + self.mode = self.MODE_RECEIVE + self.update_mode_switcher() + def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() -- cgit v1.2.3-54-g00ecf From 86fa0215d8554b40bf84648ed209cf159b94555d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:16:10 -0700 Subject: Fix small --local-only bug that causes a crash when canceling settings --- onionshare_gui/settings_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 94aa8342..07353153 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -737,7 +737,7 @@ class SettingsDialog(QtWidgets.QDialog): Cancel button clicked. """ self.common.log('SettingsDialog', 'cancel_clicked') - if not self.onion.is_authenticated(): + if not self.local_only and self.onion.is_authenticated(): Alert(self.common, strings._('gui_tor_connection_canceled', True), QtWidgets.QMessageBox.Warning) sys.exit() else: -- cgit v1.2.3-54-g00ecf From b349471c308f1c3055667ec12d9c33a6c444cf41 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:24:12 -0700 Subject: Add empty ShareMode and ReceiveMode widgets, and show and hide them when switching modes --- onionshare_gui/onionshare_gui.py | 19 +++++++++++++++++++ onionshare_gui/receive_mode.py | 30 ++++++++++++++++++++++++++++++ onionshare_gui/share_mode.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 onionshare_gui/receive_mode.py create mode 100644 onionshare_gui/share_mode.py diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 25656c69..39b75449 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -27,6 +27,9 @@ from onionshare import strings, common from onionshare.common import Common, ShutdownTimer from onionshare.onion import * +from .share_mode import ShareMode +from .receive_mode import ReceiveMode + from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog from .file_selection import FileSelection @@ -115,6 +118,11 @@ class OnionShareGui(QtWidgets.QMainWindow): mode_switcher_layout.addWidget(self.share_mode_button) mode_switcher_layout.addWidget(self.receive_mode_button) mode_switcher_layout.addWidget(self.settings_button) + + # Share and receive mode widgets + self.receive_mode = ReceiveMode(self.common) + self.share_mode = ReceiveMode(self.common) + self.update_mode_switcher() # File selection @@ -240,6 +248,9 @@ class OnionShareGui(QtWidgets.QMainWindow): # Layouts contents_layout = QtWidgets.QVBoxLayout() contents_layout.setContentsMargins(10, 10, 10, 10) + contents_layout.addWidget(self.receive_mode) + contents_layout.addWidget(self.share_mode) + # TODO: move these into ShareMode contents_layout.addWidget(self.info_widget) contents_layout.addLayout(self.file_selection) contents_layout.addWidget(self.primary_action) @@ -283,10 +294,18 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.mode == self.MODE_SHARE: self.share_mode_button.setStyleSheet(self.mode_switcher_selected_style) self.receive_mode_button.setStyleSheet(self.mode_switcher_unselected_style) + + self.share_mode.show() + self.receive_mode.hide() else: self.share_mode_button.setStyleSheet(self.mode_switcher_unselected_style) self.receive_mode_button.setStyleSheet(self.mode_switcher_selected_style) + self.share_mode.hide() + self.receive_mode.show() + + self.adjustSize(); + def share_mode_clicked(self): self.mode = self.MODE_SHARE self.update_mode_switcher() diff --git a/onionshare_gui/receive_mode.py b/onionshare_gui/receive_mode.py new file mode 100644 index 00000000..dd6f5eeb --- /dev/null +++ b/onionshare_gui/receive_mode.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +class ReceiveMode(QtWidgets.QWidget): + """ + Parts of the main window UI for receiving files. + """ + def __init__(self, common): + super(ReceiveMode, self).__init__() + self.common = common diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py new file mode 100644 index 00000000..81dd62d5 --- /dev/null +++ b/onionshare_gui/share_mode.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +class ShareMode(QtWidgets.QWidget): + """ + Parts of the main window UI for sharing files. + """ + def __init__(self, common): + super(ShareMode, self).__init__() + self.common = common -- cgit v1.2.3-54-g00ecf From ac67f6be6a848a545d58b91e6ed4720d40ad5bc5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:34:29 -0700 Subject: Move a lot of code from OnionShareGui into ShareMode, but none of it runs yet --- onionshare_gui/onionshare_gui.py | 357 -------------------------------------- onionshare_gui/share_mode.py | 361 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+), 357 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 39b75449..53858eeb 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -125,95 +125,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_mode_switcher() - # File selection - self.file_selection = FileSelection(self.common) - if filenames: - for filename in filenames: - self.file_selection.file_list.add_file(filename) - - # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) - self.server_status.server_started.connect(self.file_selection.server_started) - self.server_status.server_started.connect(self.start_server) - self.server_status.server_started.connect(self.update_server_status_indicator) - self.server_status.server_stopped.connect(self.file_selection.server_stopped) - self.server_status.server_stopped.connect(self.stop_server) - self.server_status.server_stopped.connect(self.update_server_status_indicator) - self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.cancel_server) - self.server_status.server_canceled.connect(self.file_selection.server_stopped) - self.server_status.server_canceled.connect(self.update_primary_action) - self.start_server_finished.connect(self.clear_message) - self.start_server_finished.connect(self.server_status.start_server_finished) - self.start_server_finished.connect(self.update_server_status_indicator) - self.stop_server_finished.connect(self.server_status.stop_server_finished) - self.stop_server_finished.connect(self.update_server_status_indicator) - self.file_selection.file_list.files_updated.connect(self.server_status.update) - self.file_selection.file_list.files_updated.connect(self.update_primary_action) - self.server_status.url_copied.connect(self.copy_url) - self.server_status.hidservauth_copied.connect(self.copy_hidservauth) - self.starting_server_step2.connect(self.start_server_step2) - self.starting_server_step3.connect(self.start_server_step3) - self.starting_server_error.connect(self.start_server_error) - self.server_status.button_clicked.connect(self.clear_message) - - # Filesize warning - self.filesize_warning = QtWidgets.QLabel() - self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') - self.filesize_warning.hide() - - # Downloads - self.downloads = Downloads(self.common) - self.new_download = False - self.downloads_in_progress = 0 - self.downloads_completed = 0 - - # Info label along top of screen - self.info_layout = QtWidgets.QHBoxLayout() - self.info_label = QtWidgets.QLabel() - self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.info_show_downloads = QtWidgets.QToolButton() - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.info_show_downloads.setCheckable(True) - self.info_show_downloads.toggled.connect(self.downloads_toggled) - self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) - - self.info_in_progress_downloads_count = QtWidgets.QLabel() - self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.info_completed_downloads_count = QtWidgets.QLabel() - self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.update_downloads_completed(self.downloads_in_progress) - self.update_downloads_in_progress(self.downloads_in_progress) - - self.info_layout.addWidget(self.info_label) - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_downloads_count) - self.info_layout.addWidget(self.info_completed_downloads_count) - self.info_layout.addWidget(self.info_show_downloads) - - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() - - # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) - self.server_status_image_label = QtWidgets.QLabel() - self.server_status_image_label.setFixedWidth(20) - self.server_status_label = QtWidgets.QLabel() - self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') - server_status_indicator_layout = QtWidgets.QHBoxLayout() - server_status_indicator_layout.addWidget(self.server_status_image_label) - server_status_indicator_layout.addWidget(self.server_status_label) - self.server_status_indicator = QtWidgets.QWidget() - self.server_status_indicator.setLayout(server_status_indicator_layout) - self.update_server_status_indicator() - # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) @@ -236,24 +147,11 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_share_status_label) - # Primary action layout - primary_action_layout = QtWidgets.QVBoxLayout() - primary_action_layout.addWidget(self.server_status) - primary_action_layout.addWidget(self.filesize_warning) - self.primary_action = QtWidgets.QWidget() - self.primary_action.setLayout(primary_action_layout) - self.primary_action.hide() - self.update_primary_action() - # Layouts contents_layout = QtWidgets.QVBoxLayout() contents_layout.setContentsMargins(10, 10, 10, 10) contents_layout.addWidget(self.receive_mode) contents_layout.addWidget(self.share_mode) - # TODO: move these into ShareMode - contents_layout.addWidget(self.info_widget) - contents_layout.addLayout(self.file_selection) - contents_layout.addWidget(self.primary_action) layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) @@ -265,9 +163,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() - # Always start with focus on file selection - self.file_selection.setFocus() - # The server isn't active yet self.set_server_active(False) @@ -314,46 +209,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_RECEIVE self.update_mode_switcher() - def update_primary_action(self): - # Show or hide primary action layout - file_count = self.file_selection.file_list.count() - if file_count > 0: - self.primary_action.show() - self.info_widget.show() - - # Update the file count in the info label - total_size_bytes = 0 - for index in range(self.file_selection.file_list.count()): - item = self.file_selection.file_list.item(index) - total_size_bytes += item.size_bytes - total_size_readable = self.common.human_readable_filesize(total_size_bytes) - - if file_count > 1: - self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) - else: - self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) - - else: - self.primary_action.hide() - self.info_widget.hide() - - # Resize window - self.adjustSize() - - def update_server_status_indicator(self): - self.common.log('OnionShareGui', 'update_server_status_indicator') - - # Set the status image - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) - elif self.server_status.status == self.server_status.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_working', True)) - elif self.server_status.status == self.server_status.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_started', True)) - def _initSystemTray(self): menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) @@ -445,165 +300,6 @@ class OnionShareGui(QtWidgets.QMainWindow): # When settings close, refresh the server status UI self.server_status.update() - def start_server(self): - """ - Start the onionshare server. This uses multiple threads to start the Tor onion - server and the web app. - """ - self.common.log('OnionShareGui', 'start_server') - - self.set_server_active(True) - - self.app.set_stealth(self.common.settings.get('use_stealth')) - - # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() - self.reset_info_counters() - self.status_bar.clearMessage() - self.server_share_status_label.setText('') - - # Reset web counters - self.web.download_count = 0 - self.web.error404_count = 0 - - # start the onion service in a new thread - def start_onion_service(self): - try: - self.app.start_onion_service() - self.starting_server_step2.emit() - - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: - self.starting_server_error.emit(e.args[0]) - return - - - self.app.stay_open = not self.common.settings.get('close_after_first_download') - - # start onionshare http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) - t.daemon = True - t.start() - # wait for modules in thread to load, preventing a thread-related cx_Freeze crash - time.sleep(0.2) - - self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') - self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) - self.t.daemon = True - self.t.start() - - def start_server_step2(self): - """ - Step 2 in starting the onionshare server. Zipping up files. - """ - self.common.log('OnionShareGui', 'start_server_step2') - - # add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(0) - self.filenames = [] - for index in range(self.file_selection.file_list.count()): - self.filenames.append(self.file_selection.file_list.item(index).filename) - - self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) - - # prepare the files for sending in a new thread - def finish_starting_server(self): - # prepare files to share - def _set_processed_size(x): - if self._zip_progress_bar != None: - self._zip_progress_bar.update_processed_size_signal.emit(x) - try: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - self.starting_server_step3.emit() - - # done - self.start_server_finished.emit() - except OSError as e: - self.starting_server_error.emit(e.strerror) - return - - t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) - t.daemon = True - t.start() - - def start_server_step3(self): - """ - Step 3 in starting the onionshare server. This displays the large filesize - warning, if applicable. - """ - self.common.log('OnionShareGui', 'start_server_step3') - - # Remove zip progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - # warn about sending large files over Tor - if self.web.zip_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() - - if self.common.settings.get('shutdown_timeout'): - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_status.timeout) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) - self.app.shutdown_timer.start() - # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. - else: - self.stop_server() - self.start_server_error(strings._('gui_server_started_after_timeout')) - - def start_server_error(self, error): - """ - If there's an error when trying to start the onion service - """ - self.common.log('OnionShareGui', 'start_server_error') - - self.set_server_active(False) - - Alert(self.common, error, QtWidgets.QMessageBox.Warning) - self.server_status.stop_server() - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - self.status_bar.clearMessage() - - def cancel_server(self): - """ - Cancel the server while it is preparing to start - """ - if self.t: - self.t.quit() - self.stop_server() - - def stop_server(self): - """ - Stop the onionshare server. - """ - self.common.log('OnionShareGui', 'stop_server') - - if self.server_status.status != self.server_status.STATUS_STOPPED: - try: - self.web.stop(self.app.port) - except: - # Probably we had no port to begin with (Onion service didn't start) - pass - self.app.cleanup() - # Remove ephemeral service, but don't disconnect from Tor - self.onion.cleanup(stop_tor=False) - self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.update_downloads_in_progress(0) - self.file_selection.file_list.adjustSize() - - self.set_server_active(False) - self.stop_server_finished.emit() - def check_for_updates(self): """ Check for updates in a new thread, if enabled. @@ -617,16 +313,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_thread.update_available.connect(update_available) self.update_thread.start() - @staticmethod - def _compute_total_size(filenames): - total_size = 0 - for filename in filenames: - if os.path.isfile(filename): - total_size += os.path.getsize(filename) - if os.path.isdir(filename): - total_size += Common.dir_size(filename) - return total_size - def check_for_requests(self): """ Check for messages communicated from the web app, and update the GUI accordingly. @@ -733,16 +419,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) - def downloads_toggled(self, checked): - """ - When the 'Show/hide downloads' button is toggled, show or hide the downloads window. - """ - self.common.log('OnionShareGui', 'toggle_downloads') - if checked: - self.downloads.downloads_container.show() - else: - self.downloads.downloads_container.hide() - def copy_url(self): """ When the URL gets copied to the clipboard, display this in the status bar. @@ -777,39 +453,6 @@ class OnionShareGui(QtWidgets.QMainWindow): # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) - def reset_info_counters(self): - """ - Set the info counters back to zero. - """ - self.update_downloads_completed(0) - self.update_downloads_in_progress(0) - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.downloads.no_downloads_label.show() - self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) - - def update_downloads_completed(self, count): - """ - Update the 'Downloads completed' info widget. - """ - if count == 0: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') - else: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') - self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) - - def update_downloads_in_progress(self, count): - """ - Update the 'Downloads in progress' info widget. - """ - if count == 0: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') - else: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) - self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) - def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') try: diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index 81dd62d5..b6aa02c9 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -20,6 +20,7 @@ along with this program. If not, see . from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from .mode import Mode class ShareMode(QtWidgets.QWidget): """ @@ -28,3 +29,363 @@ class ShareMode(QtWidgets.QWidget): def __init__(self, common): super(ShareMode, self).__init__() self.common = common + + # File selection + self.file_selection = FileSelection(self.common) + if filenames: + for filename in filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) + self.server_status.server_started.connect(self.file_selection.server_started) + self.server_status.server_started.connect(self.start_server) + self.server_status.server_started.connect(self.update_server_status_indicator) + self.server_status.server_stopped.connect(self.file_selection.server_stopped) + self.server_status.server_stopped.connect(self.stop_server) + self.server_status.server_stopped.connect(self.update_server_status_indicator) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.cancel_server) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.start_server_finished.connect(self.clear_message) + self.start_server_finished.connect(self.server_status.start_server_finished) + self.start_server_finished.connect(self.update_server_status_indicator) + self.stop_server_finished.connect(self.server_status.stop_server_finished) + self.stop_server_finished.connect(self.update_server_status_indicator) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + self.server_status.url_copied.connect(self.copy_url) + self.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.starting_server_step2.connect(self.start_server_step2) + self.starting_server_step3.connect(self.start_server_step3) + self.starting_server_error.connect(self.start_server_error) + self.server_status.button_clicked.connect(self.clear_message) + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') + self.filesize_warning.hide() + + # Downloads + self.downloads = Downloads(self.common) + self.new_download = False + self.downloads_in_progress = 0 + self.downloads_completed = 0 + + # Info label along top of screen + self.info_layout = QtWidgets.QHBoxLayout() + self.info_label = QtWidgets.QLabel() + self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.info_show_downloads = QtWidgets.QToolButton() + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setCheckable(True) + self.info_show_downloads.toggled.connect(self.downloads_toggled) + self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + + self.info_in_progress_downloads_count = QtWidgets.QLabel() + self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.info_completed_downloads_count = QtWidgets.QLabel() + self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.update_downloads_completed(self.downloads_in_progress) + self.update_downloads_in_progress(self.downloads_in_progress) + + self.info_layout.addWidget(self.info_label) + self.info_layout.addStretch() + self.info_layout.addWidget(self.info_in_progress_downloads_count) + self.info_layout.addWidget(self.info_completed_downloads_count) + self.info_layout.addWidget(self.info_show_downloads) + + self.info_widget = QtWidgets.QWidget() + self.info_widget.setLayout(self.info_layout) + self.info_widget.hide() + + # Server status indicator on the status bar + self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) + self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) + self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) + self.server_status_image_label = QtWidgets.QLabel() + self.server_status_image_label.setFixedWidth(20) + self.server_status_label = QtWidgets.QLabel() + self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') + server_status_indicator_layout = QtWidgets.QHBoxLayout() + server_status_indicator_layout.addWidget(self.server_status_image_label) + server_status_indicator_layout.addWidget(self.server_status_label) + self.server_status_indicator = QtWidgets.QWidget() + self.server_status_indicator.setLayout(server_status_indicator_layout) + self.update_server_status_indicator() + + # Primary action layout + primary_action_layout = QtWidgets.QVBoxLayout() + primary_action_layout.addWidget(self.server_status) + primary_action_layout.addWidget(self.filesize_warning) + self.primary_action = QtWidgets.QWidget() + self.primary_action.setLayout(primary_action_layout) + self.primary_action.hide() + self.update_primary_action() + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.info_widget) + layout.addLayout(self.file_selection) + layout.addWidget(self.primary_action) + self.setLayout(layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def update_primary_action(self): + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info_widget.show() + + # Update the file count in the info label + total_size_bytes = 0 + for index in range(self.file_selection.file_list.count()): + item = self.file_selection.file_list.item(index) + total_size_bytes += item.size_bytes + total_size_readable = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) + else: + self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + + else: + self.primary_action.hide() + self.info_widget.hide() + + # Resize window + self.adjustSize() + + def update_server_status_indicator(self): + self.common.log('OnionShareGui', 'update_server_status_indicator') + + # Set the status image + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) + self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) + elif self.server_status.status == self.server_status.STATUS_WORKING: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_label.setText(strings._('gui_status_indicator_working', True)) + elif self.server_status.status == self.server_status.STATUS_STARTED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) + self.server_status_label.setText(strings._('gui_status_indicator_started', True)) + + def start_server(self): + """ + Start the onionshare server. This uses multiple threads to start the Tor onion + server and the web app. + """ + self.common.log('OnionShareGui', 'start_server') + + self.set_server_active(True) + + self.app.set_stealth(self.common.settings.get('use_stealth')) + + # Hide and reset the downloads if we have previously shared + self.downloads.reset_downloads() + self.reset_info_counters() + self.status_bar.clearMessage() + self.server_share_status_label.setText('') + + # Reset web counters + self.web.download_count = 0 + self.web.error404_count = 0 + + # start the onion service in a new thread + def start_onion_service(self): + try: + self.app.start_onion_service() + self.starting_server_step2.emit() + + except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + self.starting_server_error.emit(e.args[0]) + return + + + self.app.stay_open = not self.common.settings.get('close_after_first_download') + + # start onionshare http service in new thread + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) + t.daemon = True + t.start() + # wait for modules in thread to load, preventing a thread-related cx_Freeze crash + time.sleep(0.2) + + self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) + self.t.daemon = True + self.t.start() + + def start_server_step2(self): + """ + Step 2 in starting the onionshare server. Zipping up files. + """ + self.common.log('OnionShareGui', 'start_server_step2') + + # add progress bar to the status bar, indicating the compressing of files. + self._zip_progress_bar = ZipProgressBar(0) + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self.status_bar.insertWidget(0, self._zip_progress_bar) + + # prepare the files for sending in a new thread + def finish_starting_server(self): + # prepare files to share + def _set_processed_size(x): + if self._zip_progress_bar != None: + self._zip_progress_bar.update_processed_size_signal.emit(x) + try: + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) + self.starting_server_step3.emit() + + # done + self.start_server_finished.emit() + except OSError as e: + self.starting_server_error.emit(e.strerror) + return + + t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) + t.daemon = True + t.start() + + def start_server_step3(self): + """ + Step 3 in starting the onionshare server. This displays the large filesize + warning, if applicable. + """ + self.common.log('OnionShareGui', 'start_server_step3') + + # Remove zip progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + # warn about sending large files over Tor + if self.web.zip_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + if self.common.settings.get('shutdown_timeout'): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_status.timeout) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) + self.app.shutdown_timer.start() + # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. + else: + self.stop_server() + self.start_server_error(strings._('gui_server_started_after_timeout')) + + def start_server_error(self, error): + """ + If there's an error when trying to start the onion service + """ + self.common.log('OnionShareGui', 'start_server_error') + + self.set_server_active(False) + + Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.server_status.stop_server() + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + self.status_bar.clearMessage() + + def cancel_server(self): + """ + Cancel the server while it is preparing to start + """ + if self.t: + self.t.quit() + self.stop_server() + + def stop_server(self): + """ + Stop the onionshare server. + """ + self.common.log('OnionShareGui', 'stop_server') + + if self.server_status.status != self.server_status.STATUS_STOPPED: + try: + self.web.stop(self.app.port) + except: + # Probably we had no port to begin with (Onion service didn't start) + pass + self.app.cleanup() + # Remove ephemeral service, but don't disconnect from Tor + self.onion.cleanup(stop_tor=False) + self.filesize_warning.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.update_downloads_in_progress(0) + self.file_selection.file_list.adjustSize() + + self.set_server_active(False) + self.stop_server_finished.emit() + + def downloads_toggled(self, checked): + """ + When the 'Show/hide downloads' button is toggled, show or hide the downloads window. + """ + self.common.log('OnionShareGui', 'toggle_downloads') + if checked: + self.downloads.downloads_container.show() + else: + self.downloads.downloads_container.hide() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.update_downloads_completed(0) + self.update_downloads_in_progress(0) + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.downloads.no_downloads_label.show() + self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) + + def update_downloads_completed(self, count): + """ + Update the 'Downloads completed' info widget. + """ + if count == 0: + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') + else: + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') + self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) + self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) + + def update_downloads_in_progress(self, count): + """ + Update the 'Downloads in progress' info widget. + """ + if count == 0: + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') + else: + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) + self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size -- cgit v1.2.3-54-g00ecf From 9b2b8155258dbff7db0ed8532aae6a2d550d2e66 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 22:08:51 -0700 Subject: Moving more of the logic into ShareMode, but still have much more testing to do --- onionshare_gui/onion_thread.py | 45 ++++++++++ onionshare_gui/onionshare_gui.py | 183 +++++++++++++-------------------------- onionshare_gui/receive_mode.py | 6 ++ onionshare_gui/share_mode.py | 152 +++++++++++++++++++++++--------- 4 files changed, 219 insertions(+), 167 deletions(-) create mode 100644 onionshare_gui/onion_thread.py diff --git a/onionshare_gui/onion_thread.py b/onionshare_gui/onion_thread.py new file mode 100644 index 00000000..0a25e891 --- /dev/null +++ b/onionshare_gui/onion_thread.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore + +class OnionThread(QtCore.QThread): + """ + A QThread for starting our Onion Service. + By using QThread rather than threading.Thread, we are able + to call quit() or terminate() on the startup if the user + decided to cancel (in which case do not proceed with obtaining + the Onion address and starting the web server). + """ + def __init__(self, common, function, kwargs=None): + super(OnionThread, self).__init__() + + self.common = common + + self.common.log('OnionThread', '__init__') + self.function = function + if not kwargs: + self.kwargs = {} + else: + self.kwargs = kwargs + + def run(self): + self.common.log('OnionThread', 'run') + + self.function(**self.kwargs) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 53858eeb..54c19bda 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -17,24 +17,16 @@ 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 . """ -import os -import threading -import time import queue from PyQt5 import QtCore, QtWidgets, QtGui -from onionshare import strings, common -from onionshare.common import Common, ShutdownTimer -from onionshare.onion import * +from onionshare import strings from .share_mode import ShareMode from .receive_mode import ReceiveMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog -from .file_selection import FileSelection -from .server_status import ServerStatus -from .downloads import Downloads from .alert import Alert from .update_checker import UpdateThread @@ -43,12 +35,6 @@ class OnionShareGui(QtWidgets.QMainWindow): OnionShareGui is the main window for the GUI that contains all of the GUI elements. """ - start_server_finished = QtCore.pyqtSignal() - stop_server_finished = QtCore.pyqtSignal() - starting_server_step2 = QtCore.pyqtSignal() - starting_server_step3 = QtCore.pyqtSignal() - starting_server_error = QtCore.pyqtSignal(str) - MODE_SHARE = 'share' MODE_RECEIVE = 'receive' @@ -67,6 +53,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.local_only = local_only self.mode = self.MODE_SHARE + self.new_download = False # For scrolling to the bottom of the downloads list self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) @@ -119,11 +106,19 @@ class OnionShareGui(QtWidgets.QMainWindow): mode_switcher_layout.addWidget(self.receive_mode_button) mode_switcher_layout.addWidget(self.settings_button) - # Share and receive mode widgets - self.receive_mode = ReceiveMode(self.common) - self.share_mode = ReceiveMode(self.common) - - self.update_mode_switcher() + # Server status indicator on the status bar + self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) + self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) + self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) + self.server_status_image_label = QtWidgets.QLabel() + self.server_status_image_label.setFixedWidth(20) + self.server_status_label = QtWidgets.QLabel() + self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') + server_status_indicator_layout = QtWidgets.QHBoxLayout() + server_status_indicator_layout.addWidget(self.server_status_image_label) + server_status_indicator_layout.addWidget(self.server_status_label) + self.server_status_indicator = QtWidgets.QWidget() + self.server_status_indicator.setLayout(server_status_indicator_layout) # Status bar self.status_bar = QtWidgets.QStatusBar() @@ -140,13 +135,27 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) - # Status bar, zip progress bar - self._zip_progress_bar = None # Status bar, sharing messages self.server_share_status_label = QtWidgets.QLabel('') self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_share_status_label) + # Share and receive mode widgets + self.share_mode = ShareMode(self.common, filenames, qtapp, app, web, self.status_bar, self.server_share_status_label) + self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) + self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) + self.share_mode.start_server_finished.connect(self.update_server_status_indicator) + self.share_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.share_mode.start_server_finished.connect(self.clear_message) + self.share_mode.server_status.button_clicked.connect(self.clear_message) + self.share_mode.server_status.url_copied.connect(self.copy_url) + self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.share_mode.set_server_active.connect(self.set_server_active) + self.receive_mode = ReceiveMode(self.common) + + self.update_mode_switcher() + self.update_server_status_indicator() + # Layouts contents_layout = QtWidgets.QVBoxLayout() contents_layout.setContentsMargins(10, 10, 10, 10) @@ -168,7 +177,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Create the timer self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.check_for_requests) + self.timer.timeout.connect(self.timer_callback) # Start the "Connecting to Tor" dialog, which calls onion.connect() tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion) @@ -209,6 +218,25 @@ class OnionShareGui(QtWidgets.QMainWindow): self.mode = self.MODE_RECEIVE self.update_mode_switcher() + def update_server_status_indicator(self): + self.common.log('OnionShareGui', 'update_server_status_indicator') + + # Share mode + if self.mode == self.MODE_SHARE: + # Set the status image + if self.share_mode.server_status.status == self.share_mode.server_status.STATUS_STOPPED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) + self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) + elif self.share_mode.server_status.status == self.share_mode.server_status.STATUS_WORKING: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_label.setText(strings._('gui_status_indicator_working', True)) + elif self.share_mode.server_status.status == self.share_mode.server_status.STATUS_STARTED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) + self.server_status_label.setText(strings._('gui_status_indicator_started', True)) + else: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) + self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) + def _initSystemTray(self): menu = QtWidgets.QMenu() self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) @@ -313,9 +341,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_thread.update_available.connect(update_available) self.update_thread.start() - def check_for_requests(self): + def timer_callback(self): """ - Check for messages communicated from the web app, and update the GUI accordingly. + Check for messages communicated from the web app, and update the GUI accordingly. Also, + call ShareMode and ReceiveMode's timer_callbacks. """ self.update() @@ -334,7 +363,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # scroll to the bottom of the dl progress bar log pane # if a new download has been added if self.new_download: - self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) + self.share_mode.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) self.new_download = False events = [] @@ -401,23 +430,10 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) - # If the auto-shutdown timer has stopped, stop the server - if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): - if self.timeout > 0: - now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.timeout) - self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) - if not self.app.shutdown_timer.is_alive(): - # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.download_count == 0 or self.web.done: - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('close_on_timeout', True)) - # A download is probably still running - hold off on stopping the share - else: - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + if self.mode == self.MODE_SHARE: + self.share_mode.timer_callback() + else: + self.receive_mode.timer_callback() def copy_url(self): """ @@ -477,84 +493,3 @@ class OnionShareGui(QtWidgets.QMainWindow): except: e.accept() - - -class ZipProgressBar(QtWidgets.QProgressBar): - update_processed_size_signal = QtCore.pyqtSignal(int) - - def __init__(self, total_files_size): - super(ZipProgressBar, self).__init__() - self.setMaximumHeight(20) - self.setMinimumWidth(200) - self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - } - - QProgressBar::chunk { - border: 0px; - background-color: #4e064f; - width: 10px; - }""" - self.setStyleSheet(cssStyleData) - - self._total_files_size = total_files_size - self._processed_size = 0 - - self.update_processed_size_signal.connect(self.update_processed_size) - - @property - def total_files_size(self): - return self._total_files_size - - @total_files_size.setter - def total_files_size(self, val): - self._total_files_size = val - - @property - def processed_size(self): - return self._processed_size - - @processed_size.setter - def processed_size(self, val): - self.update_processed_size(val) - - def update_processed_size(self, val): - self._processed_size = val - if self.processed_size < self.total_files_size: - self.setValue(int((self.processed_size * 100) / self.total_files_size)) - elif self.total_files_size != 0: - self.setValue(100) - else: - self.setValue(0) - - -class OnionThread(QtCore.QThread): - """ - A QThread for starting our Onion Service. - By using QThread rather than threading.Thread, we are able - to call quit() or terminate() on the startup if the user - decided to cancel (in which case do not proceed with obtaining - the Onion address and starting the web server). - """ - def __init__(self, common, function, kwargs=None): - super(OnionThread, self).__init__() - - self.common = common - - self.common.log('OnionThread', '__init__') - self.function = function - if not kwargs: - self.kwargs = {} - else: - self.kwargs = kwargs - - def run(self): - self.common.log('OnionThread', 'run') - - self.function(**self.kwargs) diff --git a/onionshare_gui/receive_mode.py b/onionshare_gui/receive_mode.py index dd6f5eeb..e88c4d24 100644 --- a/onionshare_gui/receive_mode.py +++ b/onionshare_gui/receive_mode.py @@ -28,3 +28,9 @@ class ReceiveMode(QtWidgets.QWidget): def __init__(self, common): super(ReceiveMode, self).__init__() self.common = common + + def timer_callback(self): + """ + This method is called regularly on a timer while receive mode is active. + """ + pass diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index b6aa02c9..cad7dc06 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -17,18 +17,41 @@ 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 . """ +import os +import threading +import time from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from .mode import Mode +from onionshare.common import Common, ShutdownTimer +from onionshare.onion import * + +from .file_selection import FileSelection +from .server_status import ServerStatus +from .downloads import Downloads +from .onion_thread import OnionThread + class ShareMode(QtWidgets.QWidget): """ Parts of the main window UI for sharing files. """ - def __init__(self, common): + start_server_finished = QtCore.pyqtSignal() + stop_server_finished = QtCore.pyqtSignal() + starting_server_step2 = QtCore.pyqtSignal() + starting_server_step3 = QtCore.pyqtSignal() + starting_server_error = QtCore.pyqtSignal(str) + set_server_active = QtCore.pyqtSignal(bool) + + def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label): super(ShareMode, self).__init__() self.common = common + self.qtapp = qtapp + self.app = app + self.web = web + + self.status_bar = status_bar + self.server_share_status_label = server_share_status_label # File selection self.file_selection = FileSelection(self.common) @@ -40,27 +63,19 @@ class ShareMode(QtWidgets.QWidget): self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) - self.server_status.server_started.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) - self.server_status.server_stopped.connect(self.update_server_status_indicator) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.cancel_server) self.server_status.server_canceled.connect(self.file_selection.server_stopped) self.server_status.server_canceled.connect(self.update_primary_action) - self.start_server_finished.connect(self.clear_message) self.start_server_finished.connect(self.server_status.start_server_finished) - self.start_server_finished.connect(self.update_server_status_indicator) self.stop_server_finished.connect(self.server_status.stop_server_finished) - self.stop_server_finished.connect(self.update_server_status_indicator) self.file_selection.file_list.files_updated.connect(self.server_status.update) self.file_selection.file_list.files_updated.connect(self.update_primary_action) - self.server_status.url_copied.connect(self.copy_url) - self.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) - self.server_status.button_clicked.connect(self.clear_message) # Filesize warning self.filesize_warning = QtWidgets.QLabel() @@ -70,7 +85,6 @@ class ShareMode(QtWidgets.QWidget): # Downloads self.downloads = Downloads(self.common) - self.new_download = False self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -104,21 +118,6 @@ class ShareMode(QtWidgets.QWidget): self.info_widget.setLayout(self.info_layout) self.info_widget.hide() - # Server status indicator on the status bar - self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png')) - self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png')) - self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) - self.server_status_image_label = QtWidgets.QLabel() - self.server_status_image_label.setFixedWidth(20) - self.server_status_label = QtWidgets.QLabel() - self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') - server_status_indicator_layout = QtWidgets.QHBoxLayout() - server_status_indicator_layout.addWidget(self.server_status_image_label) - server_status_indicator_layout.addWidget(self.server_status_label) - self.server_status_indicator = QtWidgets.QWidget() - self.server_status_indicator.setLayout(server_status_indicator_layout) - self.update_server_status_indicator() - # Primary action layout primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout.addWidget(self.server_status) @@ -128,6 +127,9 @@ class ShareMode(QtWidgets.QWidget): self.primary_action.hide() self.update_primary_action() + # Status bar, zip progress bar + self._zip_progress_bar = None + # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(self.info_widget) @@ -138,6 +140,28 @@ class ShareMode(QtWidgets.QWidget): # Always start with focus on file selection self.file_selection.setFocus() + def timer_callback(self): + """ + This method is called regularly on a timer while share mode is active. + """ + # If the auto-shutdown timer has stopped, stop the server + if self.server_status.status == self.server_status.STATUS_STARTED: + if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): + if self.timeout > 0: + now = QtCore.QDateTime.currentDateTime() + seconds_remaining = now.secsTo(self.server_status.timeout) + self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) + if not self.app.shutdown_timer.is_alive(): + # If there were no attempts to download the share, or all downloads are done, we can stop + if self.web.download_count == 0 or self.web.done: + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('close_on_timeout', True)) + # A download is probably still running - hold off on stopping the share + else: + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -164,20 +188,6 @@ class ShareMode(QtWidgets.QWidget): # Resize window self.adjustSize() - def update_server_status_indicator(self): - self.common.log('OnionShareGui', 'update_server_status_indicator') - - # Set the status image - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) - elif self.server_status.status == self.server_status.STATUS_WORKING: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_working', True)) - elif self.server_status.status == self.server_status.STATUS_STARTED: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_started', True)) - def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion @@ -185,7 +195,7 @@ class ShareMode(QtWidgets.QWidget): """ self.common.log('OnionShareGui', 'start_server') - self.set_server_active(True) + self.set_server_active.emit(True) self.app.set_stealth(self.common.settings.get('use_stealth')) @@ -296,7 +306,7 @@ class ShareMode(QtWidgets.QWidget): """ self.common.log('OnionShareGui', 'start_server_error') - self.set_server_active(False) + self.set_server_active.emit(False) Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() @@ -326,6 +336,7 @@ class ShareMode(QtWidgets.QWidget): # Probably we had no port to begin with (Onion service didn't start) pass self.app.cleanup() + # Remove ephemeral service, but don't disconnect from Tor self.onion.cleanup(stop_tor=False) self.filesize_warning.hide() @@ -334,7 +345,7 @@ class ShareMode(QtWidgets.QWidget): self.update_downloads_in_progress(0) self.file_selection.file_list.adjustSize() - self.set_server_active(False) + self.set_server_active.emit(False) self.stop_server_finished.emit() def downloads_toggled(self, checked): @@ -389,3 +400,58 @@ class ShareMode(QtWidgets.QWidget): if os.path.isdir(filename): total_size += Common.dir_size(filename) return total_size + + +class ZipProgressBar(QtWidgets.QProgressBar): + update_processed_size_signal = QtCore.pyqtSignal(int) + + def __init__(self, total_files_size): + super(ZipProgressBar, self).__init__() + self.setMaximumHeight(20) + self.setMinimumWidth(200) + self.setValue(0) + self.setFormat(strings._('zip_progress_bar_format')) + cssStyleData =""" + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + } + + QProgressBar::chunk { + border: 0px; + background-color: #4e064f; + width: 10px; + }""" + self.setStyleSheet(cssStyleData) + + self._total_files_size = total_files_size + self._processed_size = 0 + + self.update_processed_size_signal.connect(self.update_processed_size) + + @property + def total_files_size(self): + return self._total_files_size + + @total_files_size.setter + def total_files_size(self, val): + self._total_files_size = val + + @property + def processed_size(self): + return self._processed_size + + @processed_size.setter + def processed_size(self, val): + self.update_processed_size(val) + + def update_processed_size(self, val): + self._processed_size = val + if self.processed_size < self.total_files_size: + self.setValue(int((self.processed_size * 100) / self.total_files_size)) + elif self.total_files_size != 0: + self.setValue(100) + else: + self.setValue(0) -- cgit v1.2.3-54-g00ecf From bda82bc7a00f1dcce7c3ae55054a4958ff139c68 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 08:48:17 -0700 Subject: Fix crash when canceling while compressing files, and also prevent canceled share from starting when compressing finishes --- onionshare_gui/onionshare_gui.py | 5 +++++ onionshare_gui/share_mode.py | 26 +++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 54c19bda..3f24b6d2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -146,6 +146,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) self.share_mode.start_server_finished.connect(self.update_server_status_indicator) self.share_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.share_mode.stop_server_finished.connect(self.stop_server_finished) self.share_mode.start_server_finished.connect(self.clear_message) self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.url_copied.connect(self.copy_url) @@ -255,6 +256,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.systemTray.setContextMenu(menu) self.systemTray.show() + def stop_server_finished(self): + # When the server stopped, cleanup the ephemeral onion service + self.onion.cleanup(stop_tor=False) + def _tor_connection_canceled(self): """ If the user cancels before Tor finishes connecting, ask if they want to diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index cad7dc06..04bed9d7 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -193,7 +193,7 @@ class ShareMode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('OnionShareGui', 'start_server') + self.common.log('ShareMode', 'start_server') self.set_server_active.emit(True) @@ -238,7 +238,7 @@ class ShareMode(QtWidgets.QWidget): """ Step 2 in starting the onionshare server. Zipping up files. """ - self.common.log('OnionShareGui', 'start_server_step2') + self.common.log('ShareMode', 'start_server_step2') # add progress bar to the status bar, indicating the compressing of files. self._zip_progress_bar = ZipProgressBar(0) @@ -258,10 +258,11 @@ class ShareMode(QtWidgets.QWidget): try: self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(self.web.zip_filename) - self.starting_server_step3.emit() - # done - self.start_server_finished.emit() + # Only continue if the server hasn't been canceled + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.starting_server_step3.emit() + self.start_server_finished.emit() except OSError as e: self.starting_server_error.emit(e.strerror) return @@ -275,7 +276,7 @@ class ShareMode(QtWidgets.QWidget): Step 3 in starting the onionshare server. This displays the large filesize warning, if applicable. """ - self.common.log('OnionShareGui', 'start_server_step3') + self.common.log('ShareMode', 'start_server_step3') # Remove zip progress bar if self._zip_progress_bar is not None: @@ -304,7 +305,7 @@ class ShareMode(QtWidgets.QWidget): """ If there's an error when trying to start the onion service """ - self.common.log('OnionShareGui', 'start_server_error') + self.common.log('ShareMode', 'start_server_error') self.set_server_active.emit(False) @@ -327,7 +328,7 @@ class ShareMode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('OnionShareGui', 'stop_server') + self.common.log('ShareMode', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: @@ -337,8 +338,11 @@ class ShareMode(QtWidgets.QWidget): pass self.app.cleanup() - # Remove ephemeral service, but don't disconnect from Tor - self.onion.cleanup(stop_tor=False) + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -352,7 +356,7 @@ class ShareMode(QtWidgets.QWidget): """ When the 'Show/hide downloads' button is toggled, show or hide the downloads window. """ - self.common.log('OnionShareGui', 'toggle_downloads') + self.common.log('ShareMode', 'toggle_downloads') if checked: self.downloads.downloads_container.show() else: -- cgit v1.2.3-54-g00ecf From a232cfdbded439e30bb95d868fcaf387f7209acb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 08:51:39 -0700 Subject: Hide Receive Files button while share server is active --- onionshare_gui/onionshare_gui.py | 12 ++++++++---- onionshare_gui/share_mode.py | 8 ++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3f24b6d2..bc6468a0 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -151,7 +151,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) - self.share_mode.set_server_active.connect(self.set_server_active) + self.share_mode.set_share_server_active.connect(self.set_share_server_active) self.receive_mode = ReceiveMode(self.common) self.update_mode_switcher() @@ -174,7 +174,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.show() # The server isn't active yet - self.set_server_active(False) + self.set_share_server_active(False) # Create the timer self.timer = QtCore.QTimer() @@ -462,14 +462,18 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.status_bar.clearMessage() - def set_server_active(self, active): + def set_share_server_active(self, active): """ - Disable the Settings button while an OnionShare server is active. + Disable the Settings and Receive Files buttons while an Share Files server is active. """ if active: self.settings_button.hide() + self.share_mode_button.show() + self.receive_mode_button.hide() else: self.settings_button.show() + self.share_mode_button.show() + self.receive_mode_button.show() # Disable settings menu action when server is active self.settingsAction.setEnabled(not active) diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index 04bed9d7..81e54cab 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -41,7 +41,7 @@ class ShareMode(QtWidgets.QWidget): starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - set_server_active = QtCore.pyqtSignal(bool) + set_share_server_active = QtCore.pyqtSignal(bool) def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label): super(ShareMode, self).__init__() @@ -195,7 +195,7 @@ class ShareMode(QtWidgets.QWidget): """ self.common.log('ShareMode', 'start_server') - self.set_server_active.emit(True) + self.set_share_server_active.emit(True) self.app.set_stealth(self.common.settings.get('use_stealth')) @@ -307,7 +307,7 @@ class ShareMode(QtWidgets.QWidget): """ self.common.log('ShareMode', 'start_server_error') - self.set_server_active.emit(False) + self.set_share_server_active.emit(False) Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.server_status.stop_server() @@ -349,7 +349,7 @@ class ShareMode(QtWidgets.QWidget): self.update_downloads_in_progress(0) self.file_selection.file_list.adjustSize() - self.set_server_active.emit(False) + self.set_share_server_active.emit(False) self.stop_server_finished.emit() def downloads_toggled(self, checked): -- cgit v1.2.3-54-g00ecf From 1d7ec585eeb9a15d9dc74eba35ea1917073c8536 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 09:21:23 -0700 Subject: Move the share-related event logic from OnionShareGui.event_callback into ShareMode methods, and other various bugfixes related to the refactor --- onionshare_gui/onionshare_gui.py | 116 ++++++++++++--------------------------- onionshare_gui/share_mode.py | 85 +++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 81 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index bc6468a0..292564f2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -44,8 +44,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') - self._initSystemTray() - self.web = web self.onion = onion self.qtapp = qtapp @@ -53,7 +51,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.local_only = local_only self.mode = self.MODE_SHARE - self.new_download = False # For scrolling to the bottom of the downloads list self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) @@ -63,6 +60,24 @@ class OnionShareGui(QtWidgets.QMainWindow): self.config = config self.common.load_settings(self.config) + # System tray + menu = QtWidgets.QMenu() + self.settings_action = menu.addAction(strings._('gui_settings_window_title', True)) + self.settings_action.triggered.connect(self.open_settings) + help_action = menu.addAction(strings._('gui_settings_button_help', True)) + help_action.triggered.connect(SettingsDialog.help_clicked) + exit_action = menu.addAction(strings._('systray_menu_exit', True)) + exit_action.triggered.connect(self.close) + + self.system_tray = QtWidgets.QSystemTrayIcon(self) + # The convention is Mac systray icons are always grayscale + if self.common.platform == 'Darwin': + self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) + else: + self.system_tray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.system_tray.setContextMenu(menu) + self.system_tray.show() + # Mode switcher, to switch between share files and receive files self.mode_switcher_selected_style = """ QPushButton { @@ -141,7 +156,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.insertWidget(0, self.server_share_status_label) # Share and receive mode widgets - self.share_mode = ShareMode(self.common, filenames, qtapp, app, web, self.status_bar, self.server_share_status_label) + self.share_mode = ShareMode(self.common, filenames, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) self.share_mode.start_server_finished.connect(self.update_server_status_indicator) @@ -238,24 +253,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) - def _initSystemTray(self): - menu = QtWidgets.QMenu() - self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) - self.settingsAction.triggered.connect(self.open_settings) - self.helpAction = menu.addAction(strings._('gui_settings_button_help', True)) - self.helpAction.triggered.connect(SettingsDialog.help_clicked) - self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) - self.exitAction.triggered.connect(self.close) - - self.systemTray = QtWidgets.QSystemTrayIcon(self) - # The convention is Mac systray icons are always grayscale - if self.common.platform == 'Darwin': - self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png'))) - else: - self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.systemTray.setContextMenu(menu) - self.systemTray.show() - def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service self.onion.cleanup(stop_tor=False) @@ -309,6 +306,7 @@ class OnionShareGui(QtWidgets.QMainWindow): def reload_settings(): self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading') self.common.settings.load() + # We might've stopped the main requests timer if a Tor connection failed. # If we've reloaded settings, we probably succeeded in obtaining a new # connection. If so, restart the timer. @@ -322,16 +320,17 @@ class OnionShareGui(QtWidgets.QMainWindow): self.primary_action.show() self.info_widget.show() self.status_bar.clearMessage() + # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.common.settings.get('shutdown_timeout'): - self.server_status.shutdown_timeout_container.hide() + self.share_mode.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) d.settings_saved.connect(reload_settings) d.exec_() # When settings close, refresh the server status UI - self.server_status.update() + self.share_mode.server_status.update() def check_for_updates(self): """ @@ -357,19 +356,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() - self.primary_action.hide() - self.info_widget.hide() self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) + if self.system_tray.supportsMessages() and self.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) - # scroll to the bottom of the dl progress bar log pane - # if a new download has been added - if self.new_download: - self.share_mode.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) - self.new_download = False + self.share_mode.handle_tor_broke() events = [] @@ -383,54 +374,19 @@ class OnionShareGui(QtWidgets.QMainWindow): for event in events: if event["type"] == self.web.REQUEST_LOAD: - self.status_bar.showMessage(strings._('download_page_loaded', True)) + self.share_mode.handle_request_load(event) elif event["type"] == self.web.REQUEST_DOWNLOAD: - self.downloads.no_downloads_label.hide() - self.downloads.add_download(event["data"]["id"], web.zip_filesize) - self.new_download = True - self.downloads_in_progress += 1 - self.update_downloads_in_progress(self.downloads_in_progress) - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) + self.share_mode.handle_request_download(event) elif event["type"] == self.web.REQUEST_RATE_LIMIT: - self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + self.share_mode.handle_request_rate_limit(event) elif event["type"] == self.web.REQUEST_PROGRESS: - self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) - - # is the download complete? - if event["data"]["bytes"] == self.web.zip_filesize: - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) - # Update the total 'completed downloads' info - self.downloads_completed += 1 - self.update_downloads_completed(self.downloads_completed) - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) - - # close on finish? - if not self.web.stay_open: - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('closing_automatically', True)) - else: - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel_download(event["data"]["id"]) - self.downloads_in_progress = 0 - self.update_downloads_in_progress(self.downloads_in_progress) - + self.share_mode.handle_request_progress(event) elif event["type"] == self.web.REQUEST_CANCELED: - self.downloads.cancel_download(event["data"]["id"]) - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + self.share_mode.handle_request_canceled(event) elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) @@ -445,16 +401,16 @@ class OnionShareGui(QtWidgets.QMainWindow): When the URL gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_url') - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) + if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_hidservauth') - if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) + if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): """ @@ -476,7 +432,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode_button.show() # Disable settings menu action when server is active - self.settingsAction.setEnabled(not active) + self.settings_action.setEnabled(not active) def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index 81e54cab..d248f2ff 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -30,6 +30,7 @@ from .file_selection import FileSelection from .server_status import ServerStatus from .downloads import Downloads from .onion_thread import OnionThread +from .alert import Alert class ShareMode(QtWidgets.QWidget): @@ -43,7 +44,7 @@ class ShareMode(QtWidgets.QWidget): starting_server_error = QtCore.pyqtSignal(str) set_share_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label): + def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label, system_tray): super(ShareMode, self).__init__() self.common = common self.qtapp = qtapp @@ -52,6 +53,7 @@ class ShareMode(QtWidgets.QWidget): self.status_bar = status_bar self.server_share_status_label = server_share_status_label + self.system_tray = system_tray # File selection self.file_selection = FileSelection(self.common) @@ -87,6 +89,7 @@ class ShareMode(QtWidgets.QWidget): self.downloads = Downloads(self.common) self.downloads_in_progress = 0 self.downloads_completed = 0 + self.new_download = False # For scrolling to the bottom of the downloads list # Info label along top of screen self.info_layout = QtWidgets.QHBoxLayout() @@ -144,6 +147,11 @@ class ShareMode(QtWidgets.QWidget): """ This method is called regularly on a timer while share mode is active. """ + # Scroll to the bottom of the download progress bar log pane if a new download has been added + if self.new_download: + self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) + self.new_download = False + # If the auto-shutdown timer has stopped, stop the server if self.server_status.status == self.server_status.STATUS_STARTED: if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): @@ -162,6 +170,81 @@ class ShareMode(QtWidgets.QWidget): self.status_bar.clearMessage() self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + def handle_tor_broke(self): + """ + Handle connection from Tor breaking. + """ + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.primary_action.hide() + self.info_widget.hide() + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.status_bar.showMessage(strings._('download_page_loaded', True)) + + def handle_request_download(self, event): + """ + Handle REQUEST_DOWNLOAD event. + """ + self.downloads.no_downloads_label.hide() + self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) + self.new_download = True + self.downloads_in_progress += 1 + self.update_downloads_in_progress(self.downloads_in_progress) + + if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) + + def handle_request_rate_limit(self, event): + """ + Handle REQUEST_RATE_LIMIT event. + """ + self.stop_server() + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) + + # Is the download complete? + if event["data"]["bytes"] == self.web.zip_filesize: + if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) + # Update the total 'completed downloads' info + self.downloads_completed += 1 + self.update_downloads_completed(self.downloads_completed) + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.update_downloads_in_progress(self.downloads_in_progress) + + # close on finish? + if not self.web.stay_open: + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('closing_automatically', True)) + else: + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.downloads.cancel_download(event["data"]["id"]) + self.downloads_in_progress = 0 + self.update_downloads_in_progress(self.downloads_in_progress) + + def handle_request_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + self.downloads.cancel_download(event["data"]["id"]) + + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.update_downloads_in_progress(self.downloads_in_progress) + if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): + self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() -- cgit v1.2.3-54-g00ecf From 2ee7e74236c0ef57cd256a56bd110bcb58228f2a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 09:26:06 -0700 Subject: Remove the desktop notification setting -- everyone gets them now --- onionshare/settings.py | 1 - onionshare_gui/onionshare_gui.py | 9 +++------ onionshare_gui/settings_dialog.py | 13 ------------- onionshare_gui/share_mode.py | 10 ++++------ share/locale/da.json | 1 - share/locale/en.json | 1 - share/locale/nl.json | 1 - test/test_onionshare_settings.py | 2 -- 8 files changed, 7 insertions(+), 31 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 1c7638c0..a79ec9a6 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -59,7 +59,6 @@ class Settings(object): 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, - 'systray_notifications': True, 'shutdown_timeout': False, 'use_stealth': False, 'use_autoupdate': True, diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 292564f2..14c4d22a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -357,8 +357,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if not self.onion.is_authenticated(): self.timer.stop() self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.system_tray.supportsMessages() and self.settings.get('systray_notifications'): - 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', True), strings._('gui_tor_connection_error_settings', True)) self.share_mode.handle_tor_broke() @@ -401,16 +400,14 @@ class OnionShareGui(QtWidgets.QMainWindow): When the URL gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_url') - if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.system_tray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) + self.system_tray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True)) def copy_hidservauth(self): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ self.common.log('OnionShareGui', 'copy_hidservauth') - if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.system_tray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) + self.system_tray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True)) def clear_message(self): """ diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 07353153..a80ec7ff 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -59,11 +59,6 @@ class SettingsDialog(QtWidgets.QDialog): 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)) - # Whether or not to show systray notifications - self.systray_notifications_checkbox = QtWidgets.QCheckBox() - self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) - self.systray_notifications_checkbox.setText(strings._("gui_settings_systray_notifications", True)) - # Whether or not to use a shutdown timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) @@ -77,7 +72,6 @@ class SettingsDialog(QtWidgets.QDialog): # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) - sharing_group_layout.addWidget(self.systray_notifications_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) @@ -386,12 +380,6 @@ class SettingsDialog(QtWidgets.QDialog): else: self.close_after_first_download_checkbox.setCheckState(QtCore.Qt.Unchecked) - systray_notifications = self.old_settings.get('systray_notifications') - if systray_notifications: - self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Checked) - else: - self.systray_notifications_checkbox.setCheckState(QtCore.Qt.Unchecked) - shutdown_timeout = self.old_settings.get('shutdown_timeout') if shutdown_timeout: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) @@ -760,7 +748,6 @@ class SettingsDialog(QtWidgets.QDialog): settings.load() # To get the last update timestamp settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) - settings.set('systray_notifications', self.systray_notifications_checkbox.isChecked()) settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked()) if self.save_private_key_checkbox.isChecked(): settings.set('save_private_key', True) diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py index d248f2ff..008fb910 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode.py @@ -195,8 +195,7 @@ class ShareMode(QtWidgets.QWidget): self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) - if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): - 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', True), strings._('systray_download_started_message', True)) def handle_request_rate_limit(self, event): """ @@ -213,8 +212,8 @@ class ShareMode(QtWidgets.QWidget): # Is the download complete? if event["data"]["bytes"] == self.web.zip_filesize: - if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): - 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', True), strings._('systray_download_completed_message', True)) + # Update the total 'completed downloads' info self.downloads_completed += 1 self.update_downloads_completed(self.downloads_completed) @@ -242,8 +241,7 @@ class ShareMode(QtWidgets.QWidget): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress(self.downloads_in_progress) - if self.system_tray.supportsMessages() and self.common.settings.get('systray_notifications'): - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) def update_primary_action(self): # Show or hide primary action layout diff --git a/share/locale/da.json b/share/locale/da.json index 2c8d0c78..af0789b9 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -75,7 +75,6 @@ "gui_settings_autoupdate_check_button": "Søg efter opdateringer", "gui_settings_sharing_label": "Valgmuligheder for deling", "gui_settings_close_after_first_download_option": "Stop deling efter første download", - "gui_settings_systray_notifications": "Vis skrivebordsnotifikationer", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser", diff --git a/share/locale/en.json b/share/locale/en.json index 2c2a824e..7d240b59 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -86,7 +86,6 @@ "gui_settings_autoupdate_check_button": "Check For Updates", "gui_settings_sharing_label": "Sharing options", "gui_settings_close_after_first_download_option": "Stop sharing after first download", - "gui_settings_systray_notifications": "Show desktop notifications", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use Tor that is bundled with OnionShare", "gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser", diff --git a/share/locale/nl.json b/share/locale/nl.json index 3dd74664..3af1e0d8 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -74,7 +74,6 @@ "gui_settings_autoupdate_check_button": "Controleer voor update", "gui_settings_sharing_label": "Deel opties", "gui_settings_close_after_first_download_option": "Stop delen na eerste download", - "gui_settings_systray_notifications": "Laat desktop notificaties zien", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", "gui_settings_connection_type_bundled_option": "Gebruik Tor die is meegeleverd met OnionShare", "gui_settings_connection_type_automatic_option": "Probeer automatische configuratie met Tor Browser", diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 67fd7b38..116e5356 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -51,7 +51,6 @@ class TestSettings: 'auth_type': 'no_auth', 'auth_password': '', 'close_after_first_download': True, - 'systray_notifications': True, 'shutdown_timeout': False, 'use_stealth': False, 'use_autoupdate': True, @@ -119,7 +118,6 @@ class TestSettings: assert settings_obj.get('auth_type') == 'no_auth' assert settings_obj.get('auth_password') == '' assert settings_obj.get('close_after_first_download') is True - assert settings_obj.get('systray_notifications') is True assert settings_obj.get('use_stealth') is False assert settings_obj.get('use_autoupdate') is True assert settings_obj.get('autoupdate_timestamp') is None -- cgit v1.2.3-54-g00ecf From b6b61f753d6ac97ffcb2cbe5bd6300a405410947 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 10:07:59 -0700 Subject: Update GPL copyright year --- LICENSE | 2 +- dev_scripts/onionshare | 2 +- dev_scripts/onionshare-gui | 2 +- install/get-tor-osx.py | 2 +- install/get-tor-windows.py | 2 +- install/scripts/onionshare | 2 +- install/scripts/onionshare-gui | 2 +- install/scripts/onionshare-pyinstaller | 2 +- onionshare/__init__.py | 2 +- onionshare/common.py | 2 +- onionshare/onion.py | 2 +- onionshare/onionshare.py | 2 +- onionshare/settings.py | 2 +- onionshare/strings.py | 2 +- onionshare/web.py | 2 +- onionshare_gui/__init__.py | 2 +- onionshare_gui/alert.py | 2 +- onionshare_gui/downloads.py | 2 +- onionshare_gui/file_selection.py | 2 +- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/server_status.py | 2 +- onionshare_gui/settings_dialog.py | 2 +- onionshare_gui/tor_connection_dialog.py | 2 +- onionshare_gui/update_checker.py | 2 +- setup.py | 2 +- share/license.txt | 2 +- test/test_helpers.py | 2 +- test/test_onionshare.py | 2 +- test/test_onionshare_common.py | 2 +- test/test_onionshare_settings.py | 2 +- test/test_onionshare_strings.py | 2 +- test/test_onionshare_web.py | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/LICENSE b/LICENSE index dd69f276..41436ac5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ OnionShare -Copyright © 2016 +Copyright © 2014-2018 Micah Lee GNU GENERAL PUBLIC LICENSE diff --git a/dev_scripts/onionshare b/dev_scripts/onionshare index 81be89f4..29cdedcc 100755 --- a/dev_scripts/onionshare +++ b/dev_scripts/onionshare @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/dev_scripts/onionshare-gui b/dev_scripts/onionshare-gui index aab70404..40ab742c 100755 --- a/dev_scripts/onionshare-gui +++ b/dev_scripts/onionshare-gui @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index f6cac62f..d00dc19e 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index f5aeb3f7..6ecb1fb5 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/install/scripts/onionshare b/install/scripts/onionshare index 6a1529fe..e2205e04 100755 --- a/install/scripts/onionshare +++ b/install/scripts/onionshare @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/install/scripts/onionshare-gui b/install/scripts/onionshare-gui index 786277c4..fed29d83 100755 --- a/install/scripts/onionshare-gui +++ b/install/scripts/onionshare-gui @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/install/scripts/onionshare-pyinstaller b/install/scripts/onionshare-pyinstaller index c9552120..bd59b421 100644 --- a/install/scripts/onionshare-pyinstaller +++ b/install/scripts/onionshare-pyinstaller @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 0d5156d8..72848d60 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/common.py b/onionshare/common.py index 903e4148..87ec01b8 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/onion.py b/onionshare/onion.py index dc47019f..4812842a 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 10d73751..ad5ea113 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/settings.py b/onionshare/settings.py index a79ec9a6..c3311d60 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/strings.py b/onionshare/strings.py index 7a1f08a5..3e9df56d 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare/web.py b/onionshare/web.py index 7a6a848b..bc8699b9 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 11a5999c..56354d52 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/alert.py b/onionshare_gui/alert.py index 981225c6..94886d17 100644 --- a/onionshare_gui/alert.py +++ b/onionshare_gui/alert.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py index 0e85d33f..36d58790 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/downloads.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py index fbc4995b..e6123669 100644 --- a/onionshare_gui/file_selection.py +++ b/onionshare_gui/file_selection.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 14c4d22a..bcde29a6 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index ed8bc5f5..2e7310f8 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index a80ec7ff..a7426317 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index 2ee13a66..ab5a7db9 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 5dc72091..eb986d6c 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/setup.py b/setup.py index 99222ef0..a6fa3038 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/share/license.txt b/share/license.txt index 1223e5a6..77d05583 100644 --- a/share/license.txt +++ b/share/license.txt @@ -1,4 +1,4 @@ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 diff --git a/test/test_helpers.py b/test/test_helpers.py index 02db1eb8..321afbb7 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -1,7 +1,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/test/test_onionshare.py b/test/test_onionshare.py index 398fd0d3..19b488df 100644 --- a/test/test_onionshare.py +++ b/test/test_onionshare.py @@ -1,7 +1,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py index c0f9ad66..d70f2c0e 100644 --- a/test/test_onionshare_common.py +++ b/test/test_onionshare_common.py @@ -1,7 +1,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 116e5356..35e37f00 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -1,7 +1,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/test/test_onionshare_strings.py b/test/test_onionshare_strings.py index db941a26..d1daa1e5 100644 --- a/test/test_onionshare_strings.py +++ b/test/test_onionshare_strings.py @@ -2,7 +2,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index a80e7098..75473596 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -1,7 +1,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2017 Micah Lee +Copyright (C) 2014-2018 Micah Lee 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 -- cgit v1.2.3-54-g00ecf From aa8c07c43439819b3f4fcc4dff169e695ad84695 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 17:18:18 -0700 Subject: In CLI recieve mode, tell the user where to look for uploaded files --- onionshare/__init__.py | 2 ++ share/locale/en.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 0d5156d8..893d83a3 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -162,6 +162,8 @@ def main(cwd=None): print('') if receive: + print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir'))) + print('') print(strings._('receive_mode_warning')) print('') diff --git a/share/locale/en.json b/share/locale/en.json index 101b8a7c..525dab04 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -64,7 +64,7 @@ "gui_download_progress_complete": "%p%, Time Elapsed: {0:s}", "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Transfer in Progress", "gui_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", "gui_quit_warning_quit": "Quit", @@ -161,6 +161,7 @@ "info_completed_downloads_tooltip": "{} download(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", + "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", "receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", "receive_mode_received_file": "Received file: {}" } -- cgit v1.2.3-54-g00ecf From 0e9298794eed521490be24f582fe28720608a2b3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 17:18:33 -0700 Subject: Typo --- share/templates/404.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/templates/404.html b/share/templates/404.html index 10a4c8ef..b704f9f2 100644 --- a/share/templates/404.html +++ b/share/templates/404.html @@ -1,7 +1,7 @@ - OnionsShare: Error 404 + OnionShare: Error 404 -- cgit v1.2.3-54-g00ecf From a017af0748cd008ce7da0cdd0e11003a0cbaf45d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 17:26:54 -0700 Subject: Make ShareMode and ReceiveMode directories, and move ShareMode modules into its dir --- onionshare_gui/downloads.py | 159 -------- onionshare_gui/file_selection.py | 408 --------------------- onionshare_gui/receive_mode.py | 36 -- onionshare_gui/receive_mode/__init__.py | 36 ++ onionshare_gui/server_status.py | 287 --------------- onionshare_gui/share_mode.py | 542 ---------------------------- onionshare_gui/share_mode/__init__.py | 542 ++++++++++++++++++++++++++++ onionshare_gui/share_mode/downloads.py | 158 ++++++++ onionshare_gui/share_mode/file_selection.py | 409 +++++++++++++++++++++ onionshare_gui/share_mode/server_status.py | 288 +++++++++++++++ 10 files changed, 1433 insertions(+), 1432 deletions(-) delete mode 100644 onionshare_gui/downloads.py delete mode 100644 onionshare_gui/file_selection.py delete mode 100644 onionshare_gui/receive_mode.py create mode 100644 onionshare_gui/receive_mode/__init__.py delete mode 100644 onionshare_gui/server_status.py delete mode 100644 onionshare_gui/share_mode.py create mode 100644 onionshare_gui/share_mode/__init__.py create mode 100644 onionshare_gui/share_mode/downloads.py create mode 100644 onionshare_gui/share_mode/file_selection.py create mode 100644 onionshare_gui/share_mode/server_status.py diff --git a/onionshare_gui/downloads.py b/onionshare_gui/downloads.py deleted file mode 100644 index 36d58790..00000000 --- a/onionshare_gui/downloads.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -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 - - # make a new progress bar - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - font-size: 12px; - } - - QProgressBar::chunk { - background-color: #4e064f; - width: 10px; - }""" - 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(cssStyleData) - 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_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_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) - else: - pb_fmt = strings._('gui_download_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.QWidget): - """ - 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.downloads_container = QtWidgets.QScrollArea() - self.downloads_container.setWidget(self) - self.downloads_container.setWindowTitle(strings._('gui_downloads', True)) - self.downloads_container.setWidgetResizable(True) - self.downloads_container.setMaximumHeight(600) - self.downloads_container.setMinimumHeight(150) - self.downloads_container.setMinimumWidth(350) - self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) - self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) - self.downloads_container.vbar = self.downloads_container.verticalScrollBar() - - self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') - self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) - - self.downloads_layout = QtWidgets.QVBoxLayout() - - self.layout = QtWidgets.QVBoxLayout() - self.layout.addWidget(self.downloads_label) - self.layout.addWidget(self.no_downloads_label) - self.layout.addLayout(self.downloads_layout) - self.layout.addStretch() - self.setLayout(self.layout) - - def add_download(self, download_id, total_bytes): - """ - Add a new download progress bar. - """ - # 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_download(self, download_id, downloaded_bytes): - """ - Update the progress of a download progress bar. - """ - self.downloads[download_id].update(downloaded_bytes) - - def cancel_download(self, download_id): - """ - Update a download progress bar to show that it has been canceled. - """ - self.downloads[download_id].cancel() - - def reset_downloads(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 = {} diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py deleted file mode 100644 index e6123669..00000000 --- a/onionshare_gui/file_selection.py +++ /dev/null @@ -1,408 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import os -from PyQt5 import QtCore, QtWidgets, QtGui -from .alert import Alert - -from onionshare import strings - -class DropHereLabel(QtWidgets.QLabel): - """ - When there are no files or folders in the FileList yet, display the - 'drop files here' message and graphic. - """ - def __init__(self, common, parent, image=False): - self.parent = parent - super(DropHereLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - - if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) - else: - self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet('color: #999999;') - - self.hide() - - def dragEnterEvent(self, event): - self.parent.drop_here_image.hide() - self.parent.drop_here_text.hide() - event.accept() - - -class DropCountLabel(QtWidgets.QLabel): - """ - While dragging files over the FileList, this counter displays the - number of files you're dragging. - """ - def __init__(self, common, parent): - self.parent = parent - super(DropCountLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet('color: #ffffff; background-color: #f44449; font-weight: bold; padding: 5px 10px; border-radius: 10px;') - self.hide() - - def dragEnterEvent(self, event): - self.hide() - event.accept() - - -class FileList(QtWidgets.QListWidget): - """ - The list of files and folders in the GUI. - """ - files_dropped = QtCore.pyqtSignal() - files_updated = QtCore.pyqtSignal() - - def __init__(self, common, parent=None): - super(FileList, self).__init__(parent) - - self.common = common - - self.setAcceptDrops(True) - self.setIconSize(QtCore.QSize(32, 32)) - self.setSortingEnabled(True) - self.setMinimumHeight(205) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.drop_here_image = DropHereLabel(self.common, self, True) - self.drop_here_text = DropHereLabel(self.common, self, False) - self.drop_count = DropCountLabel(self.common, self) - self.resizeEvent(None) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # file list should have a background image if empty - if self.count() == 0: - self.drop_here_image.show() - self.drop_here_text.show() - else: - self.drop_here_image.hide() - self.drop_here_text.hide() - - def server_started(self): - """ - Update the GUI when the server starts, by hiding delete buttons. - """ - self.setAcceptDrops(False) - self.setCurrentItem(None) - self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - for index in range(self.count()): - self.item(index).item_button.hide() - - def server_stopped(self): - """ - Update the GUI when the server stops, by showing delete buttons. - """ - self.setAcceptDrops(True) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - for index in range(self.count()): - self.item(index).item_button.show() - - def resizeEvent(self, event): - """ - When the widget is resized, resize the drop files image and text. - """ - offset = 70 - self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) - self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) - - if self.count() > 0: - # Add and delete an empty item, to force all items to get redrawn - # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') - self.addItem(item) - self.takeItem(self.row(item)) - self.update() - - # Extend any filenames that were truncated to fit the window - # We use 200 as a rough guess at how wide the 'file size + delete button' widget is - # and extend based on the overall width minus that amount. - for index in range(self.count()): - metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) - self.item(index).setText(elided) - - - def dragEnterEvent(self, event): - """ - dragEnterEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - self.setStyleSheet('FileList { border: 3px solid #538ad0; }') - count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) - - size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) - self.drop_count.show() - event.accept() - else: - event.ignore() - - def dragLeaveEvent(self, event): - """ - dragLeaveEvent for dragging files and directories into the widget. - """ - self.setStyleSheet('FileList { border: none; }') - self.drop_count.hide() - event.accept() - self.update() - - def dragMoveEvent(self, event): - """ - dragMoveEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - """ - dropEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - for url in event.mimeData().urls(): - filename = str(url.toLocalFile()) - self.add_file(filename) - else: - event.ignore() - - self.setStyleSheet('border: none;') - self.drop_count.hide() - - self.files_dropped.emit() - - def add_file(self, filename): - """ - Add a file or directory to this widget. - """ - filenames = [] - for index in range(self.count()): - filenames.append(self.item(index).filename) - - if filename not in filenames: - if not os.access(filename, os.R_OK): - Alert(self.common, strings._("not_a_readable_file", True).format(filename)) - return - - fileinfo = QtCore.QFileInfo(filename) - ip = QtWidgets.QFileIconProvider() - icon = ip.icon(fileinfo) - - if os.path.isfile(filename): - size_bytes = fileinfo.size() - size_readable = self.common.human_readable_filesize(size_bytes) - else: - size_bytes = self.common.dir_size(filename) - size_readable = self.common.human_readable_filesize(size_bytes) - - # Create a new item - item = QtWidgets.QListWidgetItem() - item.setIcon(icon) - item.size_bytes = size_bytes - - # Item's filename attribute and size labels - item.filename = filename - item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }') - - item.basename = os.path.basename(filename.rstrip('/')) - # Use the basename as the method with which to sort the list - metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) - item.setData(QtCore.Qt.DisplayRole, elided) - - # Item's delete button - def delete_item(): - itemrow = self.row(item) - self.takeItem(itemrow) - self.files_updated.emit() - - item.item_button = QtWidgets.QPushButton() - item.item_button.setDefault(False) - item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) - item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - - # Item info widget, with a white background - item_info_layout = QtWidgets.QHBoxLayout() - item_info_layout.addWidget(item_size) - item_info_layout.addWidget(item.item_button) - item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') - item_info.setLayout(item_info_layout) - - # Create the item's widget and layouts - item_hlayout = QtWidgets.QHBoxLayout() - item_hlayout.addStretch() - item_hlayout.addWidget(item_info) - widget = QtWidgets.QWidget() - widget.setLayout(item_hlayout) - - item.setSizeHint(widget.sizeHint()) - - self.addItem(item) - self.setItemWidget(item, widget) - - self.files_updated.emit() - - -class FileSelection(QtWidgets.QVBoxLayout): - """ - The list of files and folders in the GUI, as well as buttons to add and - delete the files and folders. - """ - def __init__(self, common): - super(FileSelection, self).__init__() - - self.common = common - - self.server_on = False - - # File list - self.file_list = FileList(self.common) - self.file_list.itemSelectionChanged.connect(self.update) - self.file_list.files_dropped.connect(self.update) - self.file_list.files_updated.connect(self.update) - - # Buttons - self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) - self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) - self.delete_button.clicked.connect(self.delete) - button_layout = QtWidgets.QHBoxLayout() - button_layout.addStretch() - button_layout.addWidget(self.add_button) - button_layout.addWidget(self.delete_button) - - # Add the widgets - self.addWidget(self.file_list) - self.addLayout(button_layout) - - self.update() - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # All buttons should be hidden if the server is on - if self.server_on: - self.add_button.hide() - self.delete_button.hide() - else: - self.add_button.show() - - # Delete button should be hidden if item isn't selected - if len(self.file_list.selectedItems()) == 0: - self.delete_button.hide() - else: - self.delete_button.show() - - # Update the file list - self.file_list.update() - - def add(self): - """ - Add button clicked. - """ - file_dialog = FileDialog(caption=strings._('gui_choose_items', True)) - if file_dialog.exec_() == QtWidgets.QDialog.Accepted: - for filename in file_dialog.selectedFiles(): - self.file_list.add_file(filename) - - self.file_list.setCurrentItem(None) - self.update() - - def delete(self): - """ - Delete button clicked - """ - selected = self.file_list.selectedItems() - for item in selected: - itemrow = self.file_list.row(item) - self.file_list.takeItem(itemrow) - self.file_list.files_updated.emit() - - self.file_list.setCurrentItem(None) - self.update() - - def server_started(self): - """ - Gets called when the server starts. - """ - self.server_on = True - self.file_list.server_started() - self.update() - - def server_stopped(self): - """ - Gets called when the server stops. - """ - self.server_on = False - self.file_list.server_stopped() - self.update() - - def get_num_files(self): - """ - Returns the total number of files and folders in the list. - """ - return len(range(self.file_list.count())) - - def setFocus(self): - """ - Set the Qt app focus on the file selection box. - """ - self.file_list.setFocus() - -class FileDialog(QtWidgets.QFileDialog): - """ - Overridden version of QFileDialog which allows us to select - folders as well as, or instead of, files. - """ - def __init__(self, *args, **kwargs): - QtWidgets.QFileDialog.__init__(self, *args, **kwargs) - self.setOption(self.DontUseNativeDialog, True) - self.setOption(self.ReadOnly, True) - self.setOption(self.ShowDirsOnly, False) - self.setFileMode(self.ExistingFiles) - tree_view = self.findChild(QtWidgets.QTreeView) - tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - list_view = self.findChild(QtWidgets.QListView, "listView") - list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - - def accept(self): - QtWidgets.QDialog.accept(self) diff --git a/onionshare_gui/receive_mode.py b/onionshare_gui/receive_mode.py deleted file mode 100644 index e88c4d24..00000000 --- a/onionshare_gui/receive_mode.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - -class ReceiveMode(QtWidgets.QWidget): - """ - Parts of the main window UI for receiving files. - """ - def __init__(self, common): - super(ReceiveMode, self).__init__() - self.common = common - - def timer_callback(self): - """ - This method is called regularly on a timer while receive mode is active. - """ - pass diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py new file mode 100644 index 00000000..e88c4d24 --- /dev/null +++ b/onionshare_gui/receive_mode/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +class ReceiveMode(QtWidgets.QWidget): + """ + Parts of the main window UI for receiving files. + """ + def __init__(self, common): + super(ReceiveMode, self).__init__() + self.common = common + + def timer_callback(self): + """ + This method is called regularly on a timer while receive mode is active. + """ + pass diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py deleted file mode 100644 index 2e7310f8..00000000 --- a/onionshare_gui/server_status.py +++ /dev/null @@ -1,287 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import platform -from .alert import Alert -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - -class ServerStatus(QtWidgets.QWidget): - """ - The server status chunk of the GUI. - """ - server_started = QtCore.pyqtSignal() - server_stopped = QtCore.pyqtSignal() - server_canceled = QtCore.pyqtSignal() - button_clicked = QtCore.pyqtSignal() - url_copied = QtCore.pyqtSignal() - hidservauth_copied = QtCore.pyqtSignal() - - STATUS_STOPPED = 0 - STATUS_WORKING = 1 - STATUS_STARTED = 2 - - def __init__(self, common, qtapp, app, web, file_selection): - super(ServerStatus, self).__init__() - - self.common = common - - self.status = self.STATUS_STOPPED - - self.qtapp = qtapp - self.app = app - self.web = web - self.file_selection = file_selection - - # Shutdown timeout layout - self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) - self.shutdown_timeout = QtWidgets.QDateTimeEdit() - # Set proposed timeout to be 5 minutes into the future - self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") - self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) - self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) - shutdown_timeout_layout = QtWidgets.QHBoxLayout() - shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) - shutdown_timeout_layout.addWidget(self.shutdown_timeout) - - # Shutdown timeout container, so it can all be hidden and shown as a group - shutdown_timeout_container_layout = QtWidgets.QVBoxLayout() - shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout) - self.shutdown_timeout_container = QtWidgets.QWidget() - self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout) - self.shutdown_timeout_container.hide() - - - # Server layout - self.server_button = QtWidgets.QPushButton() - self.server_button.clicked.connect(self.server_button_clicked) - - # URL layout - url_font = QtGui.QFont() - self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True)) - self.url_description.setWordWrap(True) - self.url_description.setMinimumHeight(50) - self.url = QtWidgets.QLabel() - self.url.setFont(url_font) - self.url.setWordWrap(True) - self.url.setMinimumHeight(60) - self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') - - url_buttons_style = 'QPushButton { color: #3f7fcf; }' - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) - self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(url_buttons_style) - self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) - self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(url_buttons_style) - self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) - url_buttons_layout = QtWidgets.QHBoxLayout() - url_buttons_layout.addWidget(self.copy_url_button) - url_buttons_layout.addWidget(self.copy_hidservauth_button) - url_buttons_layout.addStretch() - - url_layout = QtWidgets.QVBoxLayout() - url_layout.addWidget(self.url_description) - url_layout.addWidget(self.url) - url_layout.addLayout(url_buttons_layout) - - # Add the widgets - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.server_button) - layout.addLayout(url_layout) - layout.addWidget(self.shutdown_timeout_container) - self.setLayout(layout) - - self.update() - - def shutdown_timeout_reset(self): - """ - Reset the timeout in the UI after stopping a share - """ - self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # Set the URL fields - if self.status == self.STATUS_STARTED: - self.url_description.show() - - info_image = self.common.get_resource_path('images/info.png') - self.url_description.setText(strings._('gui_url_description', True).format(info_image)) - # Show a Tool Tip explaining the lifecycle of this URL - if self.common.settings.get('save_private_key'): - if self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) - else: - self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) - else: - if self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) - else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - - self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) - self.url.show() - - self.copy_url_button.show() - - if self.common.settings.get('save_private_key'): - if not self.common.settings.get('slug'): - self.common.settings.set('slug', self.web.slug) - self.common.settings.save() - - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - - if self.app.stealth: - self.copy_hidservauth_button.show() - else: - self.copy_hidservauth_button.hide() - else: - self.url_description.hide() - self.url.hide() - self.copy_url_button.hide() - self.copy_hidservauth_button.hide() - - # Button - button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' - button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - if self.file_selection.get_num_files() == 0: - self.server_button.hide() - else: - self.server_button.show() - - if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(button_stopped_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_start_server', True)) - self.server_button.setToolTip('') - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.show() - elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(button_started_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_stop_server', True)) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) - elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(button_working_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - else: - self.server_button.setStyleSheet(button_working_style) - self.server_button.setEnabled(False) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - - def server_button_clicked(self): - """ - Toggle starting or stopping the server. - """ - if self.status == self.STATUS_STOPPED: - if self.common.settings.get('shutdown_timeout'): - # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) - # If the timeout has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) - else: - self.start_server() - else: - self.start_server() - elif self.status == self.STATUS_STARTED: - self.stop_server() - elif self.status == self.STATUS_WORKING: - self.cancel_server() - self.button_clicked.emit() - - def start_server(self): - """ - Start the server. - """ - self.status = self.STATUS_WORKING - self.update() - self.server_started.emit() - - def start_server_finished(self): - """ - The server has finished starting. - """ - self.status = self.STATUS_STARTED - self.copy_url() - self.update() - - def stop_server(self): - """ - Stop the server. - """ - self.status = self.STATUS_WORKING - self.shutdown_timeout_reset() - self.update() - self.server_stopped.emit() - - def cancel_server(self): - """ - Cancel the server. - """ - self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') - self.status = self.STATUS_WORKING - self.shutdown_timeout_reset() - self.update() - self.server_canceled.emit() - - def stop_server_finished(self): - """ - The server has finished stopping. - """ - self.status = self.STATUS_STOPPED - self.update() - - def copy_url(self): - """ - Copy the onionshare URL to the clipboard. - """ - url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) - - clipboard = self.qtapp.clipboard() - clipboard.setText(url) - - self.url_copied.emit() - - def copy_hidservauth(self): - """ - Copy the HidServAuth line to the clipboard. - """ - clipboard = self.qtapp.clipboard() - clipboard.setText(self.app.auth_string) - - self.hidservauth_copied.emit() diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode.py deleted file mode 100644 index 008fb910..00000000 --- a/onionshare_gui/share_mode.py +++ /dev/null @@ -1,542 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import os -import threading -import time -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from onionshare.common import Common, ShutdownTimer -from onionshare.onion import * - -from .file_selection import FileSelection -from .server_status import ServerStatus -from .downloads import Downloads -from .onion_thread import OnionThread -from .alert import Alert - - -class ShareMode(QtWidgets.QWidget): - """ - Parts of the main window UI for sharing files. - """ - start_server_finished = QtCore.pyqtSignal() - stop_server_finished = QtCore.pyqtSignal() - starting_server_step2 = QtCore.pyqtSignal() - starting_server_step3 = QtCore.pyqtSignal() - starting_server_error = QtCore.pyqtSignal(str) - set_share_server_active = QtCore.pyqtSignal(bool) - - def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label, system_tray): - super(ShareMode, self).__init__() - self.common = common - self.qtapp = qtapp - self.app = app - self.web = web - - self.status_bar = status_bar - self.server_share_status_label = server_share_status_label - self.system_tray = system_tray - - # File selection - self.file_selection = FileSelection(self.common) - if filenames: - for filename in filenames: - self.file_selection.file_list.add_file(filename) - - # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) - self.server_status.server_started.connect(self.file_selection.server_started) - self.server_status.server_started.connect(self.start_server) - self.server_status.server_stopped.connect(self.file_selection.server_stopped) - self.server_status.server_stopped.connect(self.stop_server) - self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.cancel_server) - self.server_status.server_canceled.connect(self.file_selection.server_stopped) - self.server_status.server_canceled.connect(self.update_primary_action) - self.start_server_finished.connect(self.server_status.start_server_finished) - self.stop_server_finished.connect(self.server_status.stop_server_finished) - self.file_selection.file_list.files_updated.connect(self.server_status.update) - self.file_selection.file_list.files_updated.connect(self.update_primary_action) - self.starting_server_step2.connect(self.start_server_step2) - self.starting_server_step3.connect(self.start_server_step3) - self.starting_server_error.connect(self.start_server_error) - - # Filesize warning - self.filesize_warning = QtWidgets.QLabel() - self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') - self.filesize_warning.hide() - - # Downloads - self.downloads = Downloads(self.common) - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.new_download = False # For scrolling to the bottom of the downloads list - - # Info label along top of screen - self.info_layout = QtWidgets.QHBoxLayout() - self.info_label = QtWidgets.QLabel() - self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.info_show_downloads = QtWidgets.QToolButton() - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.info_show_downloads.setCheckable(True) - self.info_show_downloads.toggled.connect(self.downloads_toggled) - self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) - - self.info_in_progress_downloads_count = QtWidgets.QLabel() - self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.info_completed_downloads_count = QtWidgets.QLabel() - self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - - self.update_downloads_completed(self.downloads_in_progress) - self.update_downloads_in_progress(self.downloads_in_progress) - - self.info_layout.addWidget(self.info_label) - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_downloads_count) - self.info_layout.addWidget(self.info_completed_downloads_count) - self.info_layout.addWidget(self.info_show_downloads) - - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() - - # Primary action layout - primary_action_layout = QtWidgets.QVBoxLayout() - primary_action_layout.addWidget(self.server_status) - primary_action_layout.addWidget(self.filesize_warning) - self.primary_action = QtWidgets.QWidget() - self.primary_action.setLayout(primary_action_layout) - self.primary_action.hide() - self.update_primary_action() - - # Status bar, zip progress bar - self._zip_progress_bar = None - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.info_widget) - layout.addLayout(self.file_selection) - layout.addWidget(self.primary_action) - self.setLayout(layout) - - # Always start with focus on file selection - self.file_selection.setFocus() - - def timer_callback(self): - """ - This method is called regularly on a timer while share mode is active. - """ - # Scroll to the bottom of the download progress bar log pane if a new download has been added - if self.new_download: - self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) - self.new_download = False - - # If the auto-shutdown timer has stopped, stop the server - if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): - if self.timeout > 0: - now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.timeout) - self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) - if not self.app.shutdown_timer.is_alive(): - # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.download_count == 0 or self.web.done: - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('close_on_timeout', True)) - # A download is probably still running - hold off on stopping the share - else: - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) - - def handle_tor_broke(self): - """ - Handle connection from Tor breaking. - """ - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() - self.primary_action.hide() - self.info_widget.hide() - - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.status_bar.showMessage(strings._('download_page_loaded', True)) - - def handle_request_download(self, event): - """ - Handle REQUEST_DOWNLOAD event. - """ - self.downloads.no_downloads_label.hide() - self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) - self.new_download = True - self.downloads_in_progress += 1 - self.update_downloads_in_progress(self.downloads_in_progress) - - self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) - - def handle_request_rate_limit(self, event): - """ - Handle REQUEST_RATE_LIMIT event. - """ - self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) - - # Is the download complete? - if event["data"]["bytes"] == self.web.zip_filesize: - self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) - - # Update the total 'completed downloads' info - self.downloads_completed += 1 - self.update_downloads_completed(self.downloads_completed) - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) - - # close on finish? - if not self.web.stay_open: - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('closing_automatically', True)) - else: - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel_download(event["data"]["id"]) - self.downloads_in_progress = 0 - self.update_downloads_in_progress(self.downloads_in_progress) - - def handle_request_canceled(self, event): - """ - Handle REQUEST_CANCELED event. - """ - self.downloads.cancel_download(event["data"]["id"]) - - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) - - def update_primary_action(self): - # Show or hide primary action layout - file_count = self.file_selection.file_list.count() - if file_count > 0: - self.primary_action.show() - self.info_widget.show() - - # Update the file count in the info label - total_size_bytes = 0 - for index in range(self.file_selection.file_list.count()): - item = self.file_selection.file_list.item(index) - total_size_bytes += item.size_bytes - total_size_readable = self.common.human_readable_filesize(total_size_bytes) - - if file_count > 1: - self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) - else: - self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) - - else: - self.primary_action.hide() - self.info_widget.hide() - - # Resize window - self.adjustSize() - - def start_server(self): - """ - Start the onionshare server. This uses multiple threads to start the Tor onion - server and the web app. - """ - self.common.log('ShareMode', 'start_server') - - self.set_share_server_active.emit(True) - - self.app.set_stealth(self.common.settings.get('use_stealth')) - - # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() - self.reset_info_counters() - self.status_bar.clearMessage() - self.server_share_status_label.setText('') - - # Reset web counters - self.web.download_count = 0 - self.web.error404_count = 0 - - # start the onion service in a new thread - def start_onion_service(self): - try: - self.app.start_onion_service() - self.starting_server_step2.emit() - - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: - self.starting_server_error.emit(e.args[0]) - return - - - self.app.stay_open = not self.common.settings.get('close_after_first_download') - - # start onionshare http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) - t.daemon = True - t.start() - # wait for modules in thread to load, preventing a thread-related cx_Freeze crash - time.sleep(0.2) - - self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') - self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) - self.t.daemon = True - self.t.start() - - def start_server_step2(self): - """ - Step 2 in starting the onionshare server. Zipping up files. - """ - self.common.log('ShareMode', 'start_server_step2') - - # add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(0) - self.filenames = [] - for index in range(self.file_selection.file_list.count()): - self.filenames.append(self.file_selection.file_list.item(index).filename) - - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) - - # prepare the files for sending in a new thread - def finish_starting_server(self): - # prepare files to share - def _set_processed_size(x): - if self._zip_progress_bar != None: - self._zip_progress_bar.update_processed_size_signal.emit(x) - try: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - - # Only continue if the server hasn't been canceled - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.starting_server_step3.emit() - self.start_server_finished.emit() - except OSError as e: - self.starting_server_error.emit(e.strerror) - return - - t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) - t.daemon = True - t.start() - - def start_server_step3(self): - """ - Step 3 in starting the onionshare server. This displays the large filesize - warning, if applicable. - """ - self.common.log('ShareMode', 'start_server_step3') - - # Remove zip progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - # warn about sending large files over Tor - if self.web.zip_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() - - if self.common.settings.get('shutdown_timeout'): - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_status.timeout) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) - self.app.shutdown_timer.start() - # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. - else: - self.stop_server() - self.start_server_error(strings._('gui_server_started_after_timeout')) - - def start_server_error(self, error): - """ - If there's an error when trying to start the onion service - """ - self.common.log('ShareMode', 'start_server_error') - - self.set_share_server_active.emit(False) - - Alert(self.common, error, QtWidgets.QMessageBox.Warning) - self.server_status.stop_server() - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - self.status_bar.clearMessage() - - def cancel_server(self): - """ - Cancel the server while it is preparing to start - """ - if self.t: - self.t.quit() - self.stop_server() - - def stop_server(self): - """ - Stop the onionshare server. - """ - self.common.log('ShareMode', 'stop_server') - - if self.server_status.status != self.server_status.STATUS_STOPPED: - try: - self.web.stop(self.app.port) - except: - # Probably we had no port to begin with (Onion service didn't start) - pass - self.app.cleanup() - - # Remove the progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.update_downloads_in_progress(0) - self.file_selection.file_list.adjustSize() - - self.set_share_server_active.emit(False) - self.stop_server_finished.emit() - - def downloads_toggled(self, checked): - """ - When the 'Show/hide downloads' button is toggled, show or hide the downloads window. - """ - self.common.log('ShareMode', 'toggle_downloads') - if checked: - self.downloads.downloads_container.show() - else: - self.downloads.downloads_container.hide() - - def reset_info_counters(self): - """ - Set the info counters back to zero. - """ - self.update_downloads_completed(0) - self.update_downloads_in_progress(0) - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.downloads.no_downloads_label.show() - self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) - - def update_downloads_completed(self, count): - """ - Update the 'Downloads completed' info widget. - """ - if count == 0: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') - else: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') - self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) - - def update_downloads_in_progress(self, count): - """ - Update the 'Downloads in progress' info widget. - """ - if count == 0: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') - else: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) - self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) - - @staticmethod - def _compute_total_size(filenames): - total_size = 0 - for filename in filenames: - if os.path.isfile(filename): - total_size += os.path.getsize(filename) - if os.path.isdir(filename): - total_size += Common.dir_size(filename) - return total_size - - -class ZipProgressBar(QtWidgets.QProgressBar): - update_processed_size_signal = QtCore.pyqtSignal(int) - - def __init__(self, total_files_size): - super(ZipProgressBar, self).__init__() - self.setMaximumHeight(20) - self.setMinimumWidth(200) - self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - } - - QProgressBar::chunk { - border: 0px; - background-color: #4e064f; - width: 10px; - }""" - self.setStyleSheet(cssStyleData) - - self._total_files_size = total_files_size - self._processed_size = 0 - - self.update_processed_size_signal.connect(self.update_processed_size) - - @property - def total_files_size(self): - return self._total_files_size - - @total_files_size.setter - def total_files_size(self, val): - self._total_files_size = val - - @property - def processed_size(self): - return self._processed_size - - @processed_size.setter - def processed_size(self, val): - self.update_processed_size(val) - - def update_processed_size(self, val): - self._processed_size = val - if self.processed_size < self.total_files_size: - self.setValue(int((self.processed_size * 100) / self.total_files_size)) - elif self.total_files_size != 0: - self.setValue(100) - else: - self.setValue(0) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py new file mode 100644 index 00000000..69af6eba --- /dev/null +++ b/onionshare_gui/share_mode/__init__.py @@ -0,0 +1,542 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import os +import threading +import time +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.common import Common, ShutdownTimer +from onionshare.onion import * + +from .file_selection import FileSelection +from .server_status import ServerStatus +from .downloads import Downloads +from ..onion_thread import OnionThread +from ..alert import Alert + + +class ShareMode(QtWidgets.QWidget): + """ + Parts of the main window UI for sharing files. + """ + start_server_finished = QtCore.pyqtSignal() + stop_server_finished = QtCore.pyqtSignal() + starting_server_step2 = QtCore.pyqtSignal() + starting_server_step3 = QtCore.pyqtSignal() + starting_server_error = QtCore.pyqtSignal(str) + set_share_server_active = QtCore.pyqtSignal(bool) + + def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label, system_tray): + super(ShareMode, self).__init__() + self.common = common + self.qtapp = qtapp + self.app = app + self.web = web + + self.status_bar = status_bar + self.server_share_status_label = server_share_status_label + self.system_tray = system_tray + + # File selection + self.file_selection = FileSelection(self.common) + if filenames: + for filename in filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) + self.server_status.server_started.connect(self.file_selection.server_started) + self.server_status.server_started.connect(self.start_server) + self.server_status.server_stopped.connect(self.file_selection.server_stopped) + self.server_status.server_stopped.connect(self.stop_server) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.cancel_server) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.start_server_finished.connect(self.server_status.start_server_finished) + self.stop_server_finished.connect(self.server_status.stop_server_finished) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + self.starting_server_step2.connect(self.start_server_step2) + self.starting_server_step3.connect(self.start_server_step3) + self.starting_server_error.connect(self.start_server_error) + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') + self.filesize_warning.hide() + + # Downloads + self.downloads = Downloads(self.common) + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.new_download = False # For scrolling to the bottom of the downloads list + + # Info label along top of screen + self.info_layout = QtWidgets.QHBoxLayout() + self.info_label = QtWidgets.QLabel() + self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.info_show_downloads = QtWidgets.QToolButton() + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.info_show_downloads.setCheckable(True) + self.info_show_downloads.toggled.connect(self.downloads_toggled) + self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + + self.info_in_progress_downloads_count = QtWidgets.QLabel() + self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.info_completed_downloads_count = QtWidgets.QLabel() + self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.update_downloads_completed(self.downloads_in_progress) + self.update_downloads_in_progress(self.downloads_in_progress) + + self.info_layout.addWidget(self.info_label) + self.info_layout.addStretch() + self.info_layout.addWidget(self.info_in_progress_downloads_count) + self.info_layout.addWidget(self.info_completed_downloads_count) + self.info_layout.addWidget(self.info_show_downloads) + + self.info_widget = QtWidgets.QWidget() + self.info_widget.setLayout(self.info_layout) + self.info_widget.hide() + + # Primary action layout + primary_action_layout = QtWidgets.QVBoxLayout() + primary_action_layout.addWidget(self.server_status) + primary_action_layout.addWidget(self.filesize_warning) + self.primary_action = QtWidgets.QWidget() + self.primary_action.setLayout(primary_action_layout) + self.primary_action.hide() + self.update_primary_action() + + # Status bar, zip progress bar + self._zip_progress_bar = None + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.info_widget) + layout.addLayout(self.file_selection) + layout.addWidget(self.primary_action) + self.setLayout(layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def timer_callback(self): + """ + This method is called regularly on a timer while share mode is active. + """ + # Scroll to the bottom of the download progress bar log pane if a new download has been added + if self.new_download: + self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) + self.new_download = False + + # If the auto-shutdown timer has stopped, stop the server + if self.server_status.status == self.server_status.STATUS_STARTED: + if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): + if self.timeout > 0: + now = QtCore.QDateTime.currentDateTime() + seconds_remaining = now.secsTo(self.server_status.timeout) + self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) + if not self.app.shutdown_timer.is_alive(): + # If there were no attempts to download the share, or all downloads are done, we can stop + if self.web.download_count == 0 or self.web.done: + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('close_on_timeout', True)) + # A download is probably still running - hold off on stopping the share + else: + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + + def handle_tor_broke(self): + """ + Handle connection from Tor breaking. + """ + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.primary_action.hide() + self.info_widget.hide() + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.status_bar.showMessage(strings._('download_page_loaded', True)) + + def handle_request_download(self, event): + """ + Handle REQUEST_DOWNLOAD event. + """ + self.downloads.no_downloads_label.hide() + self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) + self.new_download = True + self.downloads_in_progress += 1 + self.update_downloads_in_progress(self.downloads_in_progress) + + self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) + + def handle_request_rate_limit(self, event): + """ + Handle REQUEST_RATE_LIMIT event. + """ + self.stop_server() + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) + + # Is the download complete? + if event["data"]["bytes"] == self.web.zip_filesize: + self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) + + # Update the total 'completed downloads' info + self.downloads_completed += 1 + self.update_downloads_completed(self.downloads_completed) + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.update_downloads_in_progress(self.downloads_in_progress) + + # close on finish? + if not self.web.stay_open: + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_share_status_label.setText(strings._('closing_automatically', True)) + else: + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.downloads.cancel_download(event["data"]["id"]) + self.downloads_in_progress = 0 + self.update_downloads_in_progress(self.downloads_in_progress) + + def handle_request_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + self.downloads.cancel_download(event["data"]["id"]) + + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.update_downloads_in_progress(self.downloads_in_progress) + self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + + def update_primary_action(self): + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info_widget.show() + + # Update the file count in the info label + total_size_bytes = 0 + for index in range(self.file_selection.file_list.count()): + item = self.file_selection.file_list.item(index) + total_size_bytes += item.size_bytes + total_size_readable = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) + else: + self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + + else: + self.primary_action.hide() + self.info_widget.hide() + + # Resize window + self.adjustSize() + + def start_server(self): + """ + Start the onionshare server. This uses multiple threads to start the Tor onion + server and the web app. + """ + self.common.log('ShareMode', 'start_server') + + self.set_share_server_active.emit(True) + + self.app.set_stealth(self.common.settings.get('use_stealth')) + + # Hide and reset the downloads if we have previously shared + self.downloads.reset_downloads() + self.reset_info_counters() + self.status_bar.clearMessage() + self.server_share_status_label.setText('') + + # Reset web counters + self.web.download_count = 0 + self.web.error404_count = 0 + + # start the onion service in a new thread + def start_onion_service(self): + try: + self.app.start_onion_service() + self.starting_server_step2.emit() + + except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + self.starting_server_error.emit(e.args[0]) + return + + + self.app.stay_open = not self.common.settings.get('close_after_first_download') + + # start onionshare http service in new thread + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) + t.daemon = True + t.start() + # wait for modules in thread to load, preventing a thread-related cx_Freeze crash + time.sleep(0.2) + + self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) + self.t.daemon = True + self.t.start() + + def start_server_step2(self): + """ + Step 2 in starting the onionshare server. Zipping up files. + """ + self.common.log('ShareMode', 'start_server_step2') + + # add progress bar to the status bar, indicating the compressing of files. + self._zip_progress_bar = ZipProgressBar(0) + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self.status_bar.insertWidget(0, self._zip_progress_bar) + + # prepare the files for sending in a new thread + def finish_starting_server(self): + # prepare files to share + def _set_processed_size(x): + if self._zip_progress_bar != None: + self._zip_progress_bar.update_processed_size_signal.emit(x) + try: + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) + + # Only continue if the server hasn't been canceled + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.starting_server_step3.emit() + self.start_server_finished.emit() + except OSError as e: + self.starting_server_error.emit(e.strerror) + return + + t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) + t.daemon = True + t.start() + + def start_server_step3(self): + """ + Step 3 in starting the onionshare server. This displays the large filesize + warning, if applicable. + """ + self.common.log('ShareMode', 'start_server_step3') + + # Remove zip progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + # warn about sending large files over Tor + if self.web.zip_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + if self.common.settings.get('shutdown_timeout'): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_status.timeout) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) + self.app.shutdown_timer.start() + # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. + else: + self.stop_server() + self.start_server_error(strings._('gui_server_started_after_timeout')) + + def start_server_error(self, error): + """ + If there's an error when trying to start the onion service + """ + self.common.log('ShareMode', 'start_server_error') + + self.set_share_server_active.emit(False) + + Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.server_status.stop_server() + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + self.status_bar.clearMessage() + + def cancel_server(self): + """ + Cancel the server while it is preparing to start + """ + if self.t: + self.t.quit() + self.stop_server() + + def stop_server(self): + """ + Stop the onionshare server. + """ + self.common.log('ShareMode', 'stop_server') + + if self.server_status.status != self.server_status.STATUS_STOPPED: + try: + self.web.stop(self.app.port) + except: + # Probably we had no port to begin with (Onion service didn't start) + pass + self.app.cleanup() + + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + self.filesize_warning.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.update_downloads_in_progress(0) + self.file_selection.file_list.adjustSize() + + self.set_share_server_active.emit(False) + self.stop_server_finished.emit() + + def downloads_toggled(self, checked): + """ + When the 'Show/hide downloads' button is toggled, show or hide the downloads window. + """ + self.common.log('ShareMode', 'toggle_downloads') + if checked: + self.downloads.downloads_container.show() + else: + self.downloads.downloads_container.hide() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.update_downloads_completed(0) + self.update_downloads_in_progress(0) + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.downloads.no_downloads_label.show() + self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) + + def update_downloads_completed(self, count): + """ + Update the 'Downloads completed' info widget. + """ + if count == 0: + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') + else: + self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') + self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) + self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) + + def update_downloads_in_progress(self, count): + """ + Update the 'Downloads in progress' info widget. + """ + if count == 0: + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') + else: + self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') + self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) + self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size + + +class ZipProgressBar(QtWidgets.QProgressBar): + update_processed_size_signal = QtCore.pyqtSignal(int) + + def __init__(self, total_files_size): + super(ZipProgressBar, self).__init__() + self.setMaximumHeight(20) + self.setMinimumWidth(200) + self.setValue(0) + self.setFormat(strings._('zip_progress_bar_format')) + cssStyleData =""" + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + } + + QProgressBar::chunk { + border: 0px; + background-color: #4e064f; + width: 10px; + }""" + self.setStyleSheet(cssStyleData) + + self._total_files_size = total_files_size + self._processed_size = 0 + + self.update_processed_size_signal.connect(self.update_processed_size) + + @property + def total_files_size(self): + return self._total_files_size + + @total_files_size.setter + def total_files_size(self, val): + self._total_files_size = val + + @property + def processed_size(self): + return self._processed_size + + @processed_size.setter + def processed_size(self, val): + self.update_processed_size(val) + + def update_processed_size(self, val): + self._processed_size = val + if self.processed_size < self.total_files_size: + self.setValue(int((self.processed_size * 100) / self.total_files_size)) + elif self.total_files_size != 0: + self.setValue(100) + else: + self.setValue(0) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py new file mode 100644 index 00000000..31298370 --- /dev/null +++ b/onionshare_gui/share_mode/downloads.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +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 + + # make a new progress bar + cssStyleData =""" + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + font-size: 12px; + } + + QProgressBar::chunk { + background-color: #4e064f; + width: 10px; + }""" + 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(cssStyleData) + 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_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_progress_starting').format( + self.common.human_readable_filesize(downloaded_bytes)) + else: + pb_fmt = strings._('gui_download_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.QWidget): + """ + 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.downloads_container = QtWidgets.QScrollArea() + self.downloads_container.setWidget(self) + self.downloads_container.setWindowTitle(strings._('gui_downloads', True)) + self.downloads_container.setWidgetResizable(True) + self.downloads_container.setMaximumHeight(600) + self.downloads_container.setMinimumHeight(150) + self.downloads_container.setMinimumWidth(350) + self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) + self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) + self.downloads_container.vbar = self.downloads_container.verticalScrollBar() + + self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + + self.downloads_layout = QtWidgets.QVBoxLayout() + + self.layout = QtWidgets.QVBoxLayout() + self.layout.addWidget(self.downloads_label) + self.layout.addWidget(self.no_downloads_label) + self.layout.addLayout(self.downloads_layout) + self.layout.addStretch() + self.setLayout(self.layout) + + def add_download(self, download_id, total_bytes): + """ + Add a new download progress bar. + """ + # 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_download(self, download_id, downloaded_bytes): + """ + Update the progress of a download progress bar. + """ + self.downloads[download_id].update(downloaded_bytes) + + def cancel_download(self, download_id): + """ + Update a download progress bar to show that it has been canceled. + """ + self.downloads[download_id].cancel() + + def reset_downloads(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 = {} diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/share_mode/file_selection.py new file mode 100644 index 00000000..13efba24 --- /dev/null +++ b/onionshare_gui/share_mode/file_selection.py @@ -0,0 +1,409 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import os +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from ..alert import Alert + +class DropHereLabel(QtWidgets.QLabel): + """ + When there are no files or folders in the FileList yet, display the + 'drop files here' message and graphic. + """ + def __init__(self, common, parent, image=False): + self.parent = parent + super(DropHereLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + + if image: + self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + else: + self.setText(strings._('gui_drag_and_drop', True)) + self.setStyleSheet('color: #999999;') + + self.hide() + + def dragEnterEvent(self, event): + self.parent.drop_here_image.hide() + self.parent.drop_here_text.hide() + event.accept() + + +class DropCountLabel(QtWidgets.QLabel): + """ + While dragging files over the FileList, this counter displays the + number of files you're dragging. + """ + def __init__(self, common, parent): + self.parent = parent + super(DropCountLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + self.setText(strings._('gui_drag_and_drop', True)) + self.setStyleSheet('color: #ffffff; background-color: #f44449; font-weight: bold; padding: 5px 10px; border-radius: 10px;') + self.hide() + + def dragEnterEvent(self, event): + self.hide() + event.accept() + + +class FileList(QtWidgets.QListWidget): + """ + The list of files and folders in the GUI. + """ + files_dropped = QtCore.pyqtSignal() + files_updated = QtCore.pyqtSignal() + + def __init__(self, common, parent=None): + super(FileList, self).__init__(parent) + + self.common = common + + self.setAcceptDrops(True) + self.setIconSize(QtCore.QSize(32, 32)) + self.setSortingEnabled(True) + self.setMinimumHeight(205) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.drop_here_image = DropHereLabel(self.common, self, True) + self.drop_here_text = DropHereLabel(self.common, self, False) + self.drop_count = DropCountLabel(self.common, self) + self.resizeEvent(None) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # file list should have a background image if empty + if self.count() == 0: + self.drop_here_image.show() + self.drop_here_text.show() + else: + self.drop_here_image.hide() + self.drop_here_text.hide() + + def server_started(self): + """ + Update the GUI when the server starts, by hiding delete buttons. + """ + self.setAcceptDrops(False) + self.setCurrentItem(None) + self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + for index in range(self.count()): + self.item(index).item_button.hide() + + def server_stopped(self): + """ + Update the GUI when the server stops, by showing delete buttons. + """ + self.setAcceptDrops(True) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + for index in range(self.count()): + self.item(index).item_button.show() + + def resizeEvent(self, event): + """ + When the widget is resized, resize the drop files image and text. + """ + offset = 70 + self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) + self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) + + if self.count() > 0: + # Add and delete an empty item, to force all items to get redrawn + # This is ugly, but the only way I could figure out how to proceed + item = QtWidgets.QListWidgetItem('fake item') + self.addItem(item) + self.takeItem(self.row(item)) + self.update() + + # Extend any filenames that were truncated to fit the window + # We use 200 as a rough guess at how wide the 'file size + delete button' widget is + # and extend based on the overall width minus that amount. + for index in range(self.count()): + metrics = QtGui.QFontMetrics(self.item(index).font()) + elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + self.item(index).setText(elided) + + + def dragEnterEvent(self, event): + """ + dragEnterEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + self.setStyleSheet('FileList { border: 3px solid #538ad0; }') + count = len(event.mimeData().urls()) + self.drop_count.setText('+{}'.format(count)) + + size_hint = self.drop_count.sizeHint() + self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.show() + event.accept() + else: + event.ignore() + + def dragLeaveEvent(self, event): + """ + dragLeaveEvent for dragging files and directories into the widget. + """ + self.setStyleSheet('FileList { border: none; }') + self.drop_count.hide() + event.accept() + self.update() + + def dragMoveEvent(self, event): + """ + dragMoveEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + dropEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + for url in event.mimeData().urls(): + filename = str(url.toLocalFile()) + self.add_file(filename) + else: + event.ignore() + + self.setStyleSheet('border: none;') + self.drop_count.hide() + + self.files_dropped.emit() + + def add_file(self, filename): + """ + Add a file or directory to this widget. + """ + filenames = [] + for index in range(self.count()): + filenames.append(self.item(index).filename) + + if filename not in filenames: + if not os.access(filename, os.R_OK): + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) + return + + fileinfo = QtCore.QFileInfo(filename) + ip = QtWidgets.QFileIconProvider() + icon = ip.icon(fileinfo) + + if os.path.isfile(filename): + size_bytes = fileinfo.size() + size_readable = self.common.human_readable_filesize(size_bytes) + else: + size_bytes = self.common.dir_size(filename) + size_readable = self.common.human_readable_filesize(size_bytes) + + # Create a new item + item = QtWidgets.QListWidgetItem() + item.setIcon(icon) + item.size_bytes = size_bytes + + # Item's filename attribute and size labels + item.filename = filename + item_size = QtWidgets.QLabel(size_readable) + item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }') + + item.basename = os.path.basename(filename.rstrip('/')) + # Use the basename as the method with which to sort the list + metrics = QtGui.QFontMetrics(item.font()) + elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + item.setData(QtCore.Qt.DisplayRole, elided) + + # Item's delete button + def delete_item(): + itemrow = self.row(item) + self.takeItem(itemrow) + self.files_updated.emit() + + item.item_button = QtWidgets.QPushButton() + item.item_button.setDefault(False) + item.item_button.setFlat(True) + item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.clicked.connect(delete_item) + item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + + # Item info widget, with a white background + item_info_layout = QtWidgets.QHBoxLayout() + item_info_layout.addWidget(item_size) + item_info_layout.addWidget(item.item_button) + item_info = QtWidgets.QWidget() + item_info.setObjectName('item-info') + item_info.setLayout(item_info_layout) + + # Create the item's widget and layouts + item_hlayout = QtWidgets.QHBoxLayout() + item_hlayout.addStretch() + item_hlayout.addWidget(item_info) + widget = QtWidgets.QWidget() + widget.setLayout(item_hlayout) + + item.setSizeHint(widget.sizeHint()) + + self.addItem(item) + self.setItemWidget(item, widget) + + self.files_updated.emit() + + +class FileSelection(QtWidgets.QVBoxLayout): + """ + The list of files and folders in the GUI, as well as buttons to add and + delete the files and folders. + """ + def __init__(self, common): + super(FileSelection, self).__init__() + + self.common = common + + self.server_on = False + + # File list + self.file_list = FileList(self.common) + self.file_list.itemSelectionChanged.connect(self.update) + self.file_list.files_dropped.connect(self.update) + self.file_list.files_updated.connect(self.update) + + # Buttons + self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) + self.add_button.clicked.connect(self.add) + self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) + self.delete_button.clicked.connect(self.delete) + button_layout = QtWidgets.QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(self.add_button) + button_layout.addWidget(self.delete_button) + + # Add the widgets + self.addWidget(self.file_list) + self.addLayout(button_layout) + + self.update() + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # All buttons should be hidden if the server is on + if self.server_on: + self.add_button.hide() + self.delete_button.hide() + else: + self.add_button.show() + + # Delete button should be hidden if item isn't selected + if len(self.file_list.selectedItems()) == 0: + self.delete_button.hide() + else: + self.delete_button.show() + + # Update the file list + self.file_list.update() + + def add(self): + """ + Add button clicked. + """ + file_dialog = FileDialog(caption=strings._('gui_choose_items', True)) + if file_dialog.exec_() == QtWidgets.QDialog.Accepted: + for filename in file_dialog.selectedFiles(): + self.file_list.add_file(filename) + + self.file_list.setCurrentItem(None) + self.update() + + def delete(self): + """ + Delete button clicked + """ + selected = self.file_list.selectedItems() + for item in selected: + itemrow = self.file_list.row(item) + self.file_list.takeItem(itemrow) + self.file_list.files_updated.emit() + + self.file_list.setCurrentItem(None) + self.update() + + def server_started(self): + """ + Gets called when the server starts. + """ + self.server_on = True + self.file_list.server_started() + self.update() + + def server_stopped(self): + """ + Gets called when the server stops. + """ + self.server_on = False + self.file_list.server_stopped() + self.update() + + def get_num_files(self): + """ + Returns the total number of files and folders in the list. + """ + return len(range(self.file_list.count())) + + def setFocus(self): + """ + Set the Qt app focus on the file selection box. + """ + self.file_list.setFocus() + +class FileDialog(QtWidgets.QFileDialog): + """ + Overridden version of QFileDialog which allows us to select + folders as well as, or instead of, files. + """ + def __init__(self, *args, **kwargs): + QtWidgets.QFileDialog.__init__(self, *args, **kwargs) + self.setOption(self.DontUseNativeDialog, True) + self.setOption(self.ReadOnly, True) + self.setOption(self.ShowDirsOnly, False) + self.setFileMode(self.ExistingFiles) + tree_view = self.findChild(QtWidgets.QTreeView) + tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + list_view = self.findChild(QtWidgets.QListView, "listView") + list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + + def accept(self): + QtWidgets.QDialog.accept(self) diff --git a/onionshare_gui/share_mode/server_status.py b/onionshare_gui/share_mode/server_status.py new file mode 100644 index 00000000..e74c41a2 --- /dev/null +++ b/onionshare_gui/share_mode/server_status.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import platform +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from ..alert import Alert + +class ServerStatus(QtWidgets.QWidget): + """ + The server status chunk of the GUI. + """ + server_started = QtCore.pyqtSignal() + server_stopped = QtCore.pyqtSignal() + server_canceled = QtCore.pyqtSignal() + button_clicked = QtCore.pyqtSignal() + url_copied = QtCore.pyqtSignal() + hidservauth_copied = QtCore.pyqtSignal() + + STATUS_STOPPED = 0 + STATUS_WORKING = 1 + STATUS_STARTED = 2 + + def __init__(self, common, qtapp, app, web, file_selection): + super(ServerStatus, self).__init__() + + self.common = common + + self.status = self.STATUS_STOPPED + + self.qtapp = qtapp + self.app = app + self.web = web + self.file_selection = file_selection + + # Shutdown timeout layout + self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) + self.shutdown_timeout = QtWidgets.QDateTimeEdit() + # Set proposed timeout to be 5 minutes into the future + self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + shutdown_timeout_layout = QtWidgets.QHBoxLayout() + shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) + shutdown_timeout_layout.addWidget(self.shutdown_timeout) + + # Shutdown timeout container, so it can all be hidden and shown as a group + shutdown_timeout_container_layout = QtWidgets.QVBoxLayout() + shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout) + self.shutdown_timeout_container = QtWidgets.QWidget() + self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout) + self.shutdown_timeout_container.hide() + + + # Server layout + self.server_button = QtWidgets.QPushButton() + self.server_button.clicked.connect(self.server_button_clicked) + + # URL layout + url_font = QtGui.QFont() + self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True)) + self.url_description.setWordWrap(True) + self.url_description.setMinimumHeight(50) + self.url = QtWidgets.QLabel() + self.url.setFont(url_font) + self.url.setWordWrap(True) + self.url.setMinimumHeight(60) + self.url.setMinimumSize(self.url.sizeHint()) + self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') + + url_buttons_style = 'QPushButton { color: #3f7fcf; }' + self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) + self.copy_url_button.setFlat(True) + self.copy_url_button.setStyleSheet(url_buttons_style) + self.copy_url_button.clicked.connect(self.copy_url) + self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) + self.copy_hidservauth_button.setFlat(True) + self.copy_hidservauth_button.setStyleSheet(url_buttons_style) + self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) + url_buttons_layout = QtWidgets.QHBoxLayout() + url_buttons_layout.addWidget(self.copy_url_button) + url_buttons_layout.addWidget(self.copy_hidservauth_button) + url_buttons_layout.addStretch() + + url_layout = QtWidgets.QVBoxLayout() + url_layout.addWidget(self.url_description) + url_layout.addWidget(self.url) + url_layout.addLayout(url_buttons_layout) + + # Add the widgets + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.server_button) + layout.addLayout(url_layout) + layout.addWidget(self.shutdown_timeout_container) + self.setLayout(layout) + + self.update() + + def shutdown_timeout_reset(self): + """ + Reset the timeout in the UI after stopping a share + """ + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # Set the URL fields + if self.status == self.STATUS_STARTED: + self.url_description.show() + + info_image = self.common.get_resource_path('images/info.png') + self.url_description.setText(strings._('gui_url_description', True).format(info_image)) + # Show a Tool Tip explaining the lifecycle of this URL + if self.common.settings.get('save_private_key'): + if self.common.settings.get('close_after_first_download'): + self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) + else: + self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) + else: + if self.common.settings.get('close_after_first_download'): + self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) + else: + self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) + + self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) + self.url.show() + + self.copy_url_button.show() + + if self.common.settings.get('save_private_key'): + if not self.common.settings.get('slug'): + self.common.settings.set('slug', self.web.slug) + self.common.settings.save() + + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + + if self.app.stealth: + self.copy_hidservauth_button.show() + else: + self.copy_hidservauth_button.hide() + else: + self.url_description.hide() + self.url.hide() + self.copy_url_button.hide() + self.copy_hidservauth_button.hide() + + # Button + button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' + button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' + button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' + if self.file_selection.get_num_files() == 0: + self.server_button.hide() + else: + self.server_button.show() + + if self.status == self.STATUS_STOPPED: + self.server_button.setStyleSheet(button_stopped_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_start_server', True)) + self.server_button.setToolTip('') + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.show() + elif self.status == self.STATUS_STARTED: + self.server_button.setStyleSheet(button_started_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_stop_server', True)) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + elif self.status == self.STATUS_WORKING: + self.server_button.setStyleSheet(button_working_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_please_wait')) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + else: + self.server_button.setStyleSheet(button_working_style) + self.server_button.setEnabled(False) + self.server_button.setText(strings._('gui_please_wait')) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + + def server_button_clicked(self): + """ + Toggle starting or stopping the server. + """ + if self.status == self.STATUS_STOPPED: + if self.common.settings.get('shutdown_timeout'): + # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen + self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) + # If the timeout has actually passed already before the user hit Start, refuse to start the server. + if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: + Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + else: + self.start_server() + else: + self.start_server() + elif self.status == self.STATUS_STARTED: + self.stop_server() + elif self.status == self.STATUS_WORKING: + self.cancel_server() + self.button_clicked.emit() + + def start_server(self): + """ + Start the server. + """ + self.status = self.STATUS_WORKING + self.update() + self.server_started.emit() + + def start_server_finished(self): + """ + The server has finished starting. + """ + self.status = self.STATUS_STARTED + self.copy_url() + self.update() + + def stop_server(self): + """ + Stop the server. + """ + self.status = self.STATUS_WORKING + self.shutdown_timeout_reset() + self.update() + self.server_stopped.emit() + + def cancel_server(self): + """ + Cancel the server. + """ + self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.status = self.STATUS_WORKING + self.shutdown_timeout_reset() + self.update() + self.server_canceled.emit() + + def stop_server_finished(self): + """ + The server has finished stopping. + """ + self.status = self.STATUS_STOPPED + self.update() + + def copy_url(self): + """ + Copy the onionshare URL to the clipboard. + """ + url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) + + clipboard = self.qtapp.clipboard() + clipboard.setText(url) + + self.url_copied.emit() + + def copy_hidservauth(self): + """ + Copy the HidServAuth line to the clipboard. + """ + clipboard = self.qtapp.clipboard() + clipboard.setText(self.app.auth_string) + + self.hidservauth_copied.emit() -- cgit v1.2.3-54-g00ecf From dd7d97dbbbe40fad79a733afccb1a430f8524ca3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 08:43:40 -0700 Subject: Allow changing downloads_dir from SettingsDialog --- onionshare_gui/__init__.py | 2 +- onionshare_gui/alert.py | 40 ------------------ onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/settings_dialog.py | 36 +++++++++++++++- onionshare_gui/share_mode/__init__.py | 2 +- onionshare_gui/share_mode/file_selection.py | 23 +--------- onionshare_gui/share_mode/server_status.py | 2 +- onionshare_gui/tor_connection_dialog.py | 2 +- onionshare_gui/widgets.py | 65 +++++++++++++++++++++++++++++ share/locale/en.json | 5 ++- 10 files changed, 111 insertions(+), 68 deletions(-) delete mode 100644 onionshare_gui/alert.py create mode 100644 onionshare_gui/widgets.py diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 56354d52..38db94d4 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -19,7 +19,7 @@ along with this program. If not, see . """ from __future__ import division import os, sys, platform, argparse -from .alert import Alert +from .widgets import Alert from PyQt5 import QtCore, QtWidgets from onionshare import strings diff --git a/onionshare_gui/alert.py b/onionshare_gui/alert.py deleted file mode 100644 index 94886d17..00000000 --- a/onionshare_gui/alert.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -class Alert(QtWidgets.QMessageBox): - """ - An alert box dialog. - """ - def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): - super(Alert, self).__init__(None) - - self.common = common - - self.common.log('Alert', '__init__') - - self.setWindowTitle("OnionShare") - self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.setText(message) - self.setIcon(icon) - self.setStandardButtons(buttons) - - if autostart: - self.exec_() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index bcde29a6..43204122 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -27,7 +27,7 @@ from .receive_mode import ReceiveMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog -from .alert import Alert +from .widgets import Alert from .update_checker import UpdateThread class OnionShareGui(QtWidgets.QMainWindow): diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index a7426317..bdb721c3 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -24,7 +24,7 @@ from onionshare import strings, common from onionshare.settings import Settings from onionshare.onion import * -from .alert import Alert +from .widgets import Alert from .update_checker import * from .tor_connection_dialog import TorConnectionDialog @@ -77,6 +77,23 @@ class SettingsDialog(QtWidgets.QDialog): sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) + # Downloads dir + downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label', True)); + self.downloads_dir_lineedit = QtWidgets.QLineEdit() + self.downloads_dir_lineedit.setReadOnly(True) + downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button', True)) + downloads_button.clicked.connect(self.downloads_button_clicked) + downloads_layout = QtWidgets.QHBoxLayout() + downloads_layout.addWidget(downloads_label) + downloads_layout.addWidget(self.downloads_dir_lineedit) + downloads_layout.addWidget(downloads_button) + + # Receiving options layout + receiving_group_layout = QtWidgets.QVBoxLayout() + receiving_group_layout.addLayout(downloads_layout) + receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) + receiving_group.setLayout(receiving_group_layout) + # Stealth options # Stealth @@ -349,6 +366,7 @@ class SettingsDialog(QtWidgets.QDialog): # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) + left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() @@ -392,6 +410,9 @@ class SettingsDialog(QtWidgets.QDialog): else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + downloads_dir = self.old_settings.get('downloads_dir') + self.downloads_dir_lineedit.setText(downloads_dir) + use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) @@ -575,6 +596,18 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) + def downloads_button_clicked(self): + """ + Browse for a new downloads directory + """ + downloads_dir = self.downloads_dir_lineedit.text() + selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, + strings._('gui_settings_downloads_label', True), downloads_dir) + + if selected_dir: + self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir)) + self.downloads_dir_lineedit.setText(selected_dir) + def test_tor_clicked(self): """ Test Tor Settings button clicked. With the given settings, see if we can @@ -760,6 +793,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('slug', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') + settings.set('downloads_dir', self.downloads_dir_lineedit.text()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 69af6eba..8ce9d2a9 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -30,7 +30,7 @@ from .file_selection import FileSelection from .server_status import ServerStatus from .downloads import Downloads from ..onion_thread import OnionThread -from ..alert import Alert +from ..widgets import Alert class ShareMode(QtWidgets.QWidget): diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/share_mode/file_selection.py index 13efba24..aa50719b 100644 --- a/onionshare_gui/share_mode/file_selection.py +++ b/onionshare_gui/share_mode/file_selection.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ..alert import Alert +from ..widgets import Alert, AddFileDialog class DropHereLabel(QtWidgets.QLabel): """ @@ -340,7 +340,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = FileDialog(caption=strings._('gui_choose_items', True)) + file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items', True)) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) @@ -388,22 +388,3 @@ class FileSelection(QtWidgets.QVBoxLayout): Set the Qt app focus on the file selection box. """ self.file_list.setFocus() - -class FileDialog(QtWidgets.QFileDialog): - """ - Overridden version of QFileDialog which allows us to select - folders as well as, or instead of, files. - """ - def __init__(self, *args, **kwargs): - QtWidgets.QFileDialog.__init__(self, *args, **kwargs) - self.setOption(self.DontUseNativeDialog, True) - self.setOption(self.ReadOnly, True) - self.setOption(self.ShowDirsOnly, False) - self.setFileMode(self.ExistingFiles) - tree_view = self.findChild(QtWidgets.QTreeView) - tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - list_view = self.findChild(QtWidgets.QListView, "listView") - list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - - def accept(self): - QtWidgets.QDialog.accept(self) diff --git a/onionshare_gui/share_mode/server_status.py b/onionshare_gui/share_mode/server_status.py index e74c41a2..4df3de79 100644 --- a/onionshare_gui/share_mode/server_status.py +++ b/onionshare_gui/share_mode/server_status.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ..alert import Alert +from ..widgets import Alert class ServerStatus(QtWidgets.QWidget): """ diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index ab5a7db9..b3bd1fe5 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.onion import * -from .alert import Alert +from .widgets import Alert class TorConnectionDialog(QtWidgets.QProgressDialog): """ diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py new file mode 100644 index 00000000..eaa5904d --- /dev/null +++ b/onionshare_gui/widgets.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +class Alert(QtWidgets.QMessageBox): + """ + An alert box dialog. + """ + def __init__(self, common, message, icon=QtWidgets.QMessageBox.NoIcon, buttons=QtWidgets.QMessageBox.Ok, autostart=True): + super(Alert, self).__init__(None) + + self.common = common + + self.common.log('Alert', '__init__') + + self.setWindowTitle("OnionShare") + self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) + self.setText(message) + self.setIcon(icon) + self.setStandardButtons(buttons) + + if autostart: + self.exec_() + + +class AddFileDialog(QtWidgets.QFileDialog): + """ + Overridden version of QFileDialog which allows us to select folders as well + as, or instead of, files. For adding files/folders to share. + """ + def __init__(self, common, *args, **kwargs): + QtWidgets.QFileDialog.__init__(self, *args, **kwargs) + + self.common = common + self.common.log('AddFileDialog', '__init__') + + self.setOption(self.DontUseNativeDialog, True) + self.setOption(self.ReadOnly, True) + self.setOption(self.ShowDirsOnly, False) + self.setFileMode(self.ExistingFiles) + tree_view = self.findChild(QtWidgets.QTreeView) + tree_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + list_view = self.findChild(QtWidgets.QListView, "listView") + list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + + def accept(self): + self.common.log('AddFileDialog', 'accept') + QtWidgets.QDialog.accept(self) diff --git a/share/locale/en.json b/share/locale/en.json index 730fe80e..40b3e1d8 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -164,5 +164,8 @@ "receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", - "gui_mode_receive_button": "Receive Files" + "gui_mode_receive_button": "Receive Files", + "gui_settings_receiving_label": "Receiving options", + "gui_settings_downloads_label": "Save files to", + "gui_settings_downloads_button": "Browse" } -- cgit v1.2.3-54-g00ecf From f1495308347411907bb264af8076350b2d9134f2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 08:49:43 -0700 Subject: Move more logic from OnionShareGui into ShareMode, when reloading settings --- onionshare_gui/onionshare_gui.py | 6 +----- onionshare_gui/share_mode/__init__.py | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 43204122..0dd880a9 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -314,11 +314,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.onion.is_authenticated(): if not self.timer.isActive(): self.timer.start(500) - # If there were some files listed for sharing, we should be ok to - # re-enable the 'Start Sharing' button now. - if self.server_status.file_selection.get_num_files() > 0: - self.primary_action.show() - self.info_widget.show() + self.share_mode.on_reload_settings() self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 8ce9d2a9..39550cee 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -243,6 +243,15 @@ class ShareMode(QtWidgets.QWidget): self.update_downloads_in_progress(self.downloads_in_progress) self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + def on_reload_settings(self): + """ + If there were some files listed for sharing, we should be ok to re-enable + the 'Start Sharing' button now. + """ + if self.server_status.file_selection.get_num_files() > 0: + self.primary_action.show() + self.info_widget.show() + def update_primary_action(self): # Show or hide primary action layout file_count = self.file_selection.file_list.count() -- cgit v1.2.3-54-g00ecf From edd5d4f78cd08e706cebd5f66008181630e1a446 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:08:50 -0700 Subject: Bugfix, TorConnectionDialog was getting instatiated with the wrong arguements --- onionshare_gui/settings_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index bdb721c3..f8748ee4 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -737,7 +737,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() - tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) tor_con.start() self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) -- cgit v1.2.3-54-g00ecf From 10581b142110d62026d0ea7d41ec631cc980a932 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:13:05 -0700 Subject: Bugfix, settings was throwing an error and quitting when Tor was authenticated, not when it was not authenticated --- onionshare_gui/settings_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index f8748ee4..dac8d75d 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -758,7 +758,7 @@ class SettingsDialog(QtWidgets.QDialog): Cancel button clicked. """ self.common.log('SettingsDialog', 'cancel_clicked') - if not self.local_only and 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) sys.exit() else: -- cgit v1.2.3-54-g00ecf From 2fc4330ee49749ed7925281d1690a2f09cb35257 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:46:49 -0700 Subject: Add ServerStatus to ReceiveMode, and update the server status indicator to have receive statuses too --- onionshare_gui/onionshare_gui.py | 33 ++-- onionshare_gui/receive_mode/__init__.py | 25 ++- onionshare_gui/server_status.py | 291 +++++++++++++++++++++++++++++ onionshare_gui/share_mode/__init__.py | 2 +- onionshare_gui/share_mode/server_status.py | 288 ---------------------------- share/locale/en.json | 9 +- 6 files changed, 344 insertions(+), 304 deletions(-) create mode 100644 onionshare_gui/server_status.py delete mode 100644 onionshare_gui/share_mode/server_status.py diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 0dd880a9..7e4ec41d 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -29,6 +29,7 @@ from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog from .widgets import Alert from .update_checker import UpdateThread +from .server_status import ServerStatus class OnionShareGui(QtWidgets.QMainWindow): """ @@ -167,7 +168,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.set_share_server_active.connect(self.set_share_server_active) - self.receive_mode = ReceiveMode(self.common) + self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) self.update_mode_switcher() self.update_server_status_indicator() @@ -224,6 +225,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.hide() self.receive_mode.show() + self.update_server_status_indicator() self.adjustSize(); def share_mode_clicked(self): @@ -237,21 +239,30 @@ class OnionShareGui(QtWidgets.QMainWindow): def update_server_status_indicator(self): self.common.log('OnionShareGui', 'update_server_status_indicator') - # Share mode + # Set the status image + if self.mode == self.MODE_SHARE: - # Set the status image - if self.share_mode.server_status.status == self.share_mode.server_status.STATUS_STOPPED: + # Share mode + 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_label.setText(strings._('gui_status_indicator_stopped', True)) - elif self.share_mode.server_status.status == self.share_mode.server_status.STATUS_WORKING: + self.server_status_label.setText(strings._('gui_status_indicator_share_stopped', True)) + elif self.share_mode.server_status.status == ServerStatus.STATUS_WORKING: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) - self.server_status_label.setText(strings._('gui_status_indicator_working', True)) - elif self.share_mode.server_status.status == self.share_mode.server_status.STATUS_STARTED: + self.server_status_label.setText(strings._('gui_status_indicator_share_working', True)) + elif self.share_mode.server_status.status == ServerStatus.STATUS_STARTED: self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) - self.server_status_label.setText(strings._('gui_status_indicator_started', True)) + self.server_status_label.setText(strings._('gui_status_indicator_share_started', True)) else: - self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped)) - self.server_status_label.setText(strings._('gui_status_indicator_stopped', True)) + # Receive mode + 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_label.setText(strings._('gui_status_indicator_receive_stopped', True)) + elif self.receive_mode.server_status.status == ServerStatus.STATUS_WORKING: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working)) + self.server_status_label.setText(strings._('gui_status_indicator_receive_working', True)) + elif self.receive_mode.server_status.status == ServerStatus.STATUS_STARTED: + self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started)) + self.server_status_label.setText(strings._('gui_status_indicator_receive_started', True)) def stop_server_finished(self): # When the server stopped, cleanup the ephemeral onion service diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index e88c4d24..42e6a8bc 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -21,13 +21,36 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from ..server_status import ServerStatus + class ReceiveMode(QtWidgets.QWidget): """ Parts of the main window UI for receiving files. """ - def __init__(self, common): + def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray): super(ReceiveMode, self).__init__() self.common = common + self.qtapp = qtapp + self.app = app + self.web = web + + self.status_bar = status_bar + self.server_share_status_label = server_share_status_label + self.system_tray = system_tray + + # Server status + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web) + + # Primary action layout + primary_action_layout = QtWidgets.QVBoxLayout() + primary_action_layout.addWidget(self.server_status) + self.primary_action = QtWidgets.QWidget() + self.primary_action.setLayout(primary_action_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.primary_action) + self.setLayout(layout) def timer_callback(self): """ diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py new file mode 100644 index 00000000..ad16731c --- /dev/null +++ b/onionshare_gui/server_status.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import platform +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from .widgets import Alert + +class ServerStatus(QtWidgets.QWidget): + """ + The server status chunk of the GUI. + """ + server_started = QtCore.pyqtSignal() + server_stopped = QtCore.pyqtSignal() + server_canceled = QtCore.pyqtSignal() + button_clicked = QtCore.pyqtSignal() + url_copied = QtCore.pyqtSignal() + hidservauth_copied = QtCore.pyqtSignal() + + STATUS_STOPPED = 0 + STATUS_WORKING = 1 + STATUS_STARTED = 2 + + def __init__(self, common, qtapp, app, web, file_selection=None): + super(ServerStatus, self).__init__() + + self.common = common + + self.status = self.STATUS_STOPPED + + self.qtapp = qtapp + self.app = app + self.web = web + + # Only used in share mode + self.file_selection = file_selection + + # Shutdown timeout layout + self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) + self.shutdown_timeout = QtWidgets.QDateTimeEdit() + # Set proposed timeout to be 5 minutes into the future + self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) + shutdown_timeout_layout = QtWidgets.QHBoxLayout() + shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) + shutdown_timeout_layout.addWidget(self.shutdown_timeout) + + # Shutdown timeout container, so it can all be hidden and shown as a group + shutdown_timeout_container_layout = QtWidgets.QVBoxLayout() + shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout) + self.shutdown_timeout_container = QtWidgets.QWidget() + self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout) + self.shutdown_timeout_container.hide() + + + # Server layout + self.server_button = QtWidgets.QPushButton() + self.server_button.clicked.connect(self.server_button_clicked) + + # URL layout + url_font = QtGui.QFont() + self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True)) + self.url_description.setWordWrap(True) + self.url_description.setMinimumHeight(50) + self.url = QtWidgets.QLabel() + self.url.setFont(url_font) + self.url.setWordWrap(True) + self.url.setMinimumHeight(60) + self.url.setMinimumSize(self.url.sizeHint()) + self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') + + url_buttons_style = 'QPushButton { color: #3f7fcf; }' + self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) + self.copy_url_button.setFlat(True) + self.copy_url_button.setStyleSheet(url_buttons_style) + self.copy_url_button.clicked.connect(self.copy_url) + self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) + self.copy_hidservauth_button.setFlat(True) + self.copy_hidservauth_button.setStyleSheet(url_buttons_style) + self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) + url_buttons_layout = QtWidgets.QHBoxLayout() + url_buttons_layout.addWidget(self.copy_url_button) + url_buttons_layout.addWidget(self.copy_hidservauth_button) + url_buttons_layout.addStretch() + + url_layout = QtWidgets.QVBoxLayout() + url_layout.addWidget(self.url_description) + url_layout.addWidget(self.url) + url_layout.addLayout(url_buttons_layout) + + # Add the widgets + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.server_button) + layout.addLayout(url_layout) + layout.addWidget(self.shutdown_timeout_container) + self.setLayout(layout) + + self.update() + + def shutdown_timeout_reset(self): + """ + Reset the timeout in the UI after stopping a share + """ + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # Set the URL fields + if self.status == self.STATUS_STARTED: + self.url_description.show() + + info_image = self.common.get_resource_path('images/info.png') + self.url_description.setText(strings._('gui_url_description', True).format(info_image)) + # Show a Tool Tip explaining the lifecycle of this URL + if self.common.settings.get('save_private_key'): + if self.common.settings.get('close_after_first_download'): + self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) + else: + self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) + else: + if self.common.settings.get('close_after_first_download'): + self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) + else: + self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) + + self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) + self.url.show() + + self.copy_url_button.show() + + if self.common.settings.get('save_private_key'): + if not self.common.settings.get('slug'): + self.common.settings.set('slug', self.web.slug) + self.common.settings.save() + + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + + if self.app.stealth: + self.copy_hidservauth_button.show() + else: + self.copy_hidservauth_button.hide() + else: + self.url_description.hide() + self.url.hide() + self.copy_url_button.hide() + self.copy_hidservauth_button.hide() + + # Button + button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' + button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' + button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' + + if self.file_selection and self.file_selection.get_num_files() == 0: + self.server_button.hide() + else: + self.server_button.show() + + if self.status == self.STATUS_STOPPED: + self.server_button.setStyleSheet(button_stopped_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_start_server', True)) + self.server_button.setToolTip('') + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.show() + elif self.status == self.STATUS_STARTED: + self.server_button.setStyleSheet(button_started_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_stop_server', True)) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + elif self.status == self.STATUS_WORKING: + self.server_button.setStyleSheet(button_working_style) + self.server_button.setEnabled(True) + self.server_button.setText(strings._('gui_please_wait')) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + else: + self.server_button.setStyleSheet(button_working_style) + self.server_button.setEnabled(False) + self.server_button.setText(strings._('gui_please_wait')) + if self.common.settings.get('shutdown_timeout'): + self.shutdown_timeout_container.hide() + + def server_button_clicked(self): + """ + Toggle starting or stopping the server. + """ + if self.status == self.STATUS_STOPPED: + if self.common.settings.get('shutdown_timeout'): + # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen + self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) + # If the timeout has actually passed already before the user hit Start, refuse to start the server. + if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: + Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + else: + self.start_server() + else: + self.start_server() + elif self.status == self.STATUS_STARTED: + self.stop_server() + elif self.status == self.STATUS_WORKING: + self.cancel_server() + self.button_clicked.emit() + + def start_server(self): + """ + Start the server. + """ + self.status = self.STATUS_WORKING + self.update() + self.server_started.emit() + + def start_server_finished(self): + """ + The server has finished starting. + """ + self.status = self.STATUS_STARTED + self.copy_url() + self.update() + + def stop_server(self): + """ + Stop the server. + """ + self.status = self.STATUS_WORKING + self.shutdown_timeout_reset() + self.update() + self.server_stopped.emit() + + def cancel_server(self): + """ + Cancel the server. + """ + self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') + self.status = self.STATUS_WORKING + self.shutdown_timeout_reset() + self.update() + self.server_canceled.emit() + + def stop_server_finished(self): + """ + The server has finished stopping. + """ + self.status = self.STATUS_STOPPED + self.update() + + def copy_url(self): + """ + Copy the onionshare URL to the clipboard. + """ + url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) + + clipboard = self.qtapp.clipboard() + clipboard.setText(url) + + self.url_copied.emit() + + def copy_hidservauth(self): + """ + Copy the HidServAuth line to the clipboard. + """ + clipboard = self.qtapp.clipboard() + clipboard.setText(self.app.auth_string) + + self.hidservauth_copied.emit() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 39550cee..754bf097 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -27,8 +27,8 @@ from onionshare.common import Common, ShutdownTimer from onionshare.onion import * from .file_selection import FileSelection -from .server_status import ServerStatus from .downloads import Downloads +from ..server_status import ServerStatus from ..onion_thread import OnionThread from ..widgets import Alert diff --git a/onionshare_gui/share_mode/server_status.py b/onionshare_gui/share_mode/server_status.py deleted file mode 100644 index 4df3de79..00000000 --- a/onionshare_gui/share_mode/server_status.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import platform -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - -from ..widgets import Alert - -class ServerStatus(QtWidgets.QWidget): - """ - The server status chunk of the GUI. - """ - server_started = QtCore.pyqtSignal() - server_stopped = QtCore.pyqtSignal() - server_canceled = QtCore.pyqtSignal() - button_clicked = QtCore.pyqtSignal() - url_copied = QtCore.pyqtSignal() - hidservauth_copied = QtCore.pyqtSignal() - - STATUS_STOPPED = 0 - STATUS_WORKING = 1 - STATUS_STARTED = 2 - - def __init__(self, common, qtapp, app, web, file_selection): - super(ServerStatus, self).__init__() - - self.common = common - - self.status = self.STATUS_STOPPED - - self.qtapp = qtapp - self.app = app - self.web = web - self.file_selection = file_selection - - # Shutdown timeout layout - self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) - self.shutdown_timeout = QtWidgets.QDateTimeEdit() - # Set proposed timeout to be 5 minutes into the future - self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") - self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) - self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) - shutdown_timeout_layout = QtWidgets.QHBoxLayout() - shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) - shutdown_timeout_layout.addWidget(self.shutdown_timeout) - - # Shutdown timeout container, so it can all be hidden and shown as a group - shutdown_timeout_container_layout = QtWidgets.QVBoxLayout() - shutdown_timeout_container_layout.addLayout(shutdown_timeout_layout) - self.shutdown_timeout_container = QtWidgets.QWidget() - self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout) - self.shutdown_timeout_container.hide() - - - # Server layout - self.server_button = QtWidgets.QPushButton() - self.server_button.clicked.connect(self.server_button_clicked) - - # URL layout - url_font = QtGui.QFont() - self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True)) - self.url_description.setWordWrap(True) - self.url_description.setMinimumHeight(50) - self.url = QtWidgets.QLabel() - self.url.setFont(url_font) - self.url.setWordWrap(True) - self.url.setMinimumHeight(60) - self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') - - url_buttons_style = 'QPushButton { color: #3f7fcf; }' - self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) - self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(url_buttons_style) - self.copy_url_button.clicked.connect(self.copy_url) - self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) - self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(url_buttons_style) - self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) - url_buttons_layout = QtWidgets.QHBoxLayout() - url_buttons_layout.addWidget(self.copy_url_button) - url_buttons_layout.addWidget(self.copy_hidservauth_button) - url_buttons_layout.addStretch() - - url_layout = QtWidgets.QVBoxLayout() - url_layout.addWidget(self.url_description) - url_layout.addWidget(self.url) - url_layout.addLayout(url_buttons_layout) - - # Add the widgets - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.server_button) - layout.addLayout(url_layout) - layout.addWidget(self.shutdown_timeout_container) - self.setLayout(layout) - - self.update() - - def shutdown_timeout_reset(self): - """ - Reset the timeout in the UI after stopping a share - """ - self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # Set the URL fields - if self.status == self.STATUS_STARTED: - self.url_description.show() - - info_image = self.common.get_resource_path('images/info.png') - self.url_description.setText(strings._('gui_url_description', True).format(info_image)) - # Show a Tool Tip explaining the lifecycle of this URL - if self.common.settings.get('save_private_key'): - if self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) - else: - self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) - else: - if self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) - else: - self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - - self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) - self.url.show() - - self.copy_url_button.show() - - if self.common.settings.get('save_private_key'): - if not self.common.settings.get('slug'): - self.common.settings.set('slug', self.web.slug) - self.common.settings.save() - - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - - if self.app.stealth: - self.copy_hidservauth_button.show() - else: - self.copy_hidservauth_button.hide() - else: - self.url_description.hide() - self.url.hide() - self.copy_url_button.hide() - self.copy_hidservauth_button.hide() - - # Button - button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' - button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - if self.file_selection.get_num_files() == 0: - self.server_button.hide() - else: - self.server_button.show() - - if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(button_stopped_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_start_server', True)) - self.server_button.setToolTip('') - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.show() - elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(button_started_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_stop_server', True)) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) - elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(button_working_style) - self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - else: - self.server_button.setStyleSheet(button_working_style) - self.server_button.setEnabled(False) - self.server_button.setText(strings._('gui_please_wait')) - if self.common.settings.get('shutdown_timeout'): - self.shutdown_timeout_container.hide() - - def server_button_clicked(self): - """ - Toggle starting or stopping the server. - """ - if self.status == self.STATUS_STOPPED: - if self.common.settings.get('shutdown_timeout'): - # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) - # If the timeout has actually passed already before the user hit Start, refuse to start the server. - if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) - else: - self.start_server() - else: - self.start_server() - elif self.status == self.STATUS_STARTED: - self.stop_server() - elif self.status == self.STATUS_WORKING: - self.cancel_server() - self.button_clicked.emit() - - def start_server(self): - """ - Start the server. - """ - self.status = self.STATUS_WORKING - self.update() - self.server_started.emit() - - def start_server_finished(self): - """ - The server has finished starting. - """ - self.status = self.STATUS_STARTED - self.copy_url() - self.update() - - def stop_server(self): - """ - Stop the server. - """ - self.status = self.STATUS_WORKING - self.shutdown_timeout_reset() - self.update() - self.server_stopped.emit() - - def cancel_server(self): - """ - Cancel the server. - """ - self.common.log('ServerStatus', 'cancel_server', 'Canceling the server mid-startup') - self.status = self.STATUS_WORKING - self.shutdown_timeout_reset() - self.update() - self.server_canceled.emit() - - def stop_server_finished(self): - """ - The server has finished stopping. - """ - self.status = self.STATUS_STOPPED - self.update() - - def copy_url(self): - """ - Copy the onionshare URL to the clipboard. - """ - url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) - - clipboard = self.qtapp.clipboard() - clipboard.setText(url) - - self.url_copied.emit() - - def copy_hidservauth(self): - """ - Copy the HidServAuth line to the clipboard. - """ - clipboard = self.qtapp.clipboard() - clipboard.setText(self.app.auth_string) - - self.hidservauth_copied.emit() diff --git a/share/locale/en.json b/share/locale/en.json index 40b3e1d8..71322735 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -151,9 +151,12 @@ "gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.", "gui_url_label_onetime": "This share will expire after the first download", "gui_url_label_onetime_and_persistent": "This share will expire after the first download

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", - "gui_status_indicator_stopped": "Ready to Share", - "gui_status_indicator_working": "Starting…", - "gui_status_indicator_started": "Sharing", + "gui_status_indicator_share_stopped": "Ready to Share", + "gui_status_indicator_share_working": "Starting…", + "gui_status_indicator_share_started": "Sharing", + "gui_status_indicator_receive_stopped": "Ready to Receive", + "gui_status_indicator_receive_working": "Starting…", + "gui_status_indicator_receive_started": "Receiving", "gui_file_info": "{} Files, {}", "gui_file_info_single": "{} File, {}", "info_in_progress_downloads_tooltip": "{} download(s) in progress", -- cgit v1.2.3-54-g00ecf From 996f1d3a818a65b2a970df0238b7d3c0ba5d8c79 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:14:27 -0700 Subject: Make different strings for start server button for different modes --- onionshare_gui/server_status.py | 25 ++++++++++++++++++------- onionshare_gui/share_mode/__init__.py | 2 +- share/locale/cs.json | 4 ++-- share/locale/da.json | 4 ++-- share/locale/de.json | 4 ++-- share/locale/en.json | 12 ++++++++---- share/locale/eo.json | 4 ++-- share/locale/es.json | 4 ++-- share/locale/fi.json | 4 ++-- share/locale/fr.json | 4 ++-- share/locale/it.json | 4 ++-- share/locale/nl.json | 2 -- share/locale/tr.json | 4 ++-- 13 files changed, 45 insertions(+), 32 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index ad16731c..54f54582 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -39,7 +39,7 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, file_selection=None): + def __init__(self, common, qtapp, app, web, share_mode, file_selection=None): super(ServerStatus, self).__init__() self.common = common @@ -51,7 +51,9 @@ class ServerStatus(QtWidgets.QWidget): self.web = web # Only used in share mode - self.file_selection = file_selection + self.share_mode = share_mode + if self.share_mode: + self.file_selection = file_selection # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) @@ -73,7 +75,6 @@ class ServerStatus(QtWidgets.QWidget): self.shutdown_timeout_container.setLayout(shutdown_timeout_container_layout) self.shutdown_timeout_container.hide() - # Server layout self.server_button = QtWidgets.QPushButton() self.server_button.clicked.connect(self.server_button_clicked) @@ -175,7 +176,7 @@ class ServerStatus(QtWidgets.QWidget): button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - if self.file_selection and self.file_selection.get_num_files() == 0: + if self.share_mode and self.file_selection.get_num_files() == 0: self.server_button.hide() else: self.server_button.show() @@ -183,17 +184,27 @@ class ServerStatus(QtWidgets.QWidget): if self.status == self.STATUS_STOPPED: self.server_button.setStyleSheet(button_stopped_style) self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_start_server', True)) + if self.share_mode: + self.server_button.setText(strings._('gui_share_start_server', True)) + else: + self.server_button.setText(strings._('gui_receive_start_server', True)) self.server_button.setToolTip('') if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() elif self.status == self.STATUS_STARTED: self.server_button.setStyleSheet(button_started_style) self.server_button.setEnabled(True) - self.server_button.setText(strings._('gui_stop_server', True)) + if self.share_mode: + self.server_button.setText(strings._('gui_share_stop_server', True)) + else: + self.server_button.setText(strings._('gui_share_stop_server', True)) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() - self.server_button.setToolTip(strings._('gui_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + if self.share_mode: + self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + else: + self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) + elif self.status == self.STATUS_WORKING: self.server_button.setStyleSheet(button_working_style) self.server_button.setEnabled(True) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 754bf097..2a13682f 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -158,7 +158,7 @@ class ShareMode(QtWidgets.QWidget): if self.timeout > 0: now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) - self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining)) + self.server_status.server_button.setText(strings._('gui_share_stop_server_shutdown_timeout', True).format(seconds_remaining)) if not self.app.shutdown_timer.is_alive(): # If there were no attempts to download the share, or all downloads are done, we can stop if self.web.download_count == 0 or self.web.done: diff --git a/share/locale/cs.json b/share/locale/cs.json index 53525ea3..5c40bcdc 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -25,8 +25,8 @@ "gui_add": "Přidat", "gui_delete": "Smazat", "gui_choose_items": "Vybrat", - "gui_start_server": "Spustit sdílení", - "gui_stop_server": "Zastavit sdílení", + "gui_share_start_server": "Spustit sdílení", + "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", "gui_downloads": "Stahování:", diff --git a/share/locale/da.json b/share/locale/da.json index af0789b9..be4b462f 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -38,8 +38,8 @@ "gui_add": "Tilføj", "gui_delete": "Slet", "gui_choose_items": "Vælg", - "gui_start_server": "Start deling", - "gui_stop_server": "Stop deling", + "gui_share_start_server": "Start deling", + "gui_share_stop_server": "Stop deling", "gui_copy_url": "Kopiér URL", "gui_copy_hidservauth": "Kopiér HidServAuth", "gui_downloads": "Downloads:", diff --git a/share/locale/de.json b/share/locale/de.json index 7347e031..8e87b89b 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -25,8 +25,8 @@ "gui_add": "Hinzufügen", "gui_delete": "Löschen", "gui_choose_items": "Auswählen", - "gui_start_server": "Server starten", - "gui_stop_server": "Server anhalten", + "gui_share_start_server": "Server starten", + "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", "gui_downloads": "Downloads:", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", diff --git a/share/locale/en.json b/share/locale/en.json index 71322735..b59ec25f 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -41,10 +41,14 @@ "gui_add": "Add", "gui_delete": "Delete", "gui_choose_items": "Choose", - "gui_start_server": "Start Sharing", - "gui_stop_server": "Stop Sharing", - "gui_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", - "gui_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", + "gui_share_start_server": "Start Sharing", + "gui_share_stop_server": "Stop Sharing", + "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", + "gui_receive_start_server": "Start Receive Mode", + "gui_receive_stop_server": "Stop Receive Mode", + "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Receive mode will expire automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", "gui_downloads": "Download History", diff --git a/share/locale/eo.json b/share/locale/eo.json index 8060f815..fb037a87 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -25,8 +25,8 @@ "gui_add": "Aldoni", "gui_delete": "Forviŝi", "gui_choose_items": "Elekti", - "gui_start_server": "Komenci kundividon", - "gui_stop_server": "Ĉesigi kundividon", + "gui_share_start_server": "Komenci kundividon", + "gui_share_stop_server": "Ĉesigi kundividon", "gui_copy_url": "Kopii URL", "gui_copy_hidservauth": "Kopii HidServAuth", "gui_downloads": "Elŝutoj:", diff --git a/share/locale/es.json b/share/locale/es.json index 5d9f8dcd..412fb501 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -24,8 +24,8 @@ "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", - "gui_start_server": "Encender el Servidor", - "gui_stop_server": "Detener el Servidor", + "gui_share_start_server": "Encender el Servidor", + "gui_share_stop_server": "Detener el Servidor", "gui_copy_url": "Copiar URL", "gui_downloads": "Descargas:", "gui_copied_url": "Se copió la URL en el portapapeles" diff --git a/share/locale/fi.json b/share/locale/fi.json index 25fda84b..00768528 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -26,8 +26,8 @@ "gui_add": "Lisää", "gui_delete": "Poista", "gui_choose_items": "Valitse", - "gui_start_server": "Käynnistä palvelin", - "gui_stop_server": "Pysäytä palvelin", + "gui_share_start_server": "Käynnistä palvelin", + "gui_share_stop_server": "Pysäytä palvelin", "gui_copy_url": "Kopioi URL-osoite", "gui_downloads": "Lataukset:", "gui_canceled": "Peruutettu", diff --git a/share/locale/fr.json b/share/locale/fr.json index a661cd26..6ec20b3b 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -29,8 +29,8 @@ "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", - "gui_start_server": "Démarrer le serveur", - "gui_stop_server": "Arrêter le serveur", + "gui_share_start_server": "Démarrer le serveur", + "gui_share_stop_server": "Arrêter le serveur", "gui_copy_url": "Copier URL", "gui_copy_hidservauth": "Copier HidServAuth", "gui_downloads": "Téléchargements :", diff --git a/share/locale/it.json b/share/locale/it.json index 75532c34..7ad38169 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -26,8 +26,8 @@ "gui_add": "Aggiungi", "gui_delete": "Cancella", "gui_choose_items": "Scegli", - "gui_start_server": "Inizia la condivisione", - "gui_stop_server": "Ferma la condivisione", + "gui_share_start_server": "Inizia la condivisione", + "gui_share_stop_server": "Ferma la condivisione", "gui_copy_url": "Copia lo URL", "gui_downloads": "Downloads:", "gui_canceled": "Cancellati", diff --git a/share/locale/nl.json b/share/locale/nl.json index 3af1e0d8..4c5cfe76 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -38,8 +38,6 @@ "gui_add": "Toevoegen", "gui_delete": "Verwijder", "gui_choose_items": "Kies", - "gui_start_server": "Start server", - "gui_stop_server": "Stop server", "gui_copy_url": "Kopieer URL", "gui_copy_hidservauth": "Kopieer HidServAuth", "gui_downloads": "Downloads:", diff --git a/share/locale/tr.json b/share/locale/tr.json index bd74f5f2..d8097909 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -26,8 +26,8 @@ "gui_add": "Ekle", "gui_delete": "Sil", "gui_choose_items": "Seç", - "gui_start_server": "Paylaşımı Başlat", - "gui_stop_server": "Paylaşımı Durdur", + "gui_share_start_server": "Paylaşımı Başlat", + "gui_share_stop_server": "Paylaşımı Durdur", "gui_copy_url": "URL Kopyala", "gui_downloads": "İndirilenler:", "gui_canceled": "İptal edilen", -- cgit v1.2.3-54-g00ecf From 81382318dcbc49161fbd02f8b7dcae73514e8164 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:22:29 -0700 Subject: Forgot to change args passed into ServerStatus --- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/share_mode/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 42e6a8bc..aad8978e 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -39,7 +39,7 @@ class ReceiveMode(QtWidgets.QWidget): self.system_tray = system_tray # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) # Primary action layout primary_action_layout = QtWidgets.QVBoxLayout() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 2a13682f..3a7add92 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -62,7 +62,7 @@ class ShareMode(QtWidgets.QWidget): self.file_selection.file_list.add_file(filename) # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, True, self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.file_selection.server_stopped) -- cgit v1.2.3-54-g00ecf From df346ad0ab21c7659d573b9801322f9cac27c6f4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:50:56 -0700 Subject: Add receive mode warning --- onionshare_gui/receive_mode/__init__.py | 6 ++++++ share/locale/en.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index aad8978e..4c74a60a 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -38,6 +38,11 @@ class ReceiveMode(QtWidgets.QWidget): self.server_share_status_label = server_share_status_label self.system_tray = system_tray + # Receive mode info + self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) + self.receive_info.setMinimumHeight(80) + self.receive_info.setWordWrap(True) + # Server status self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) @@ -49,6 +54,7 @@ class ReceiveMode(QtWidgets.QWidget): # Layout layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.receive_info) layout.addWidget(self.primary_action) self.setLayout(layout) diff --git a/share/locale/en.json b/share/locale/en.json index b59ec25f..4b1ebb09 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -168,7 +168,8 @@ "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", - "receive_mode_warning": "Warning: Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", + "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", + "gui_receive_mode_warning": "Some files can hack your computer if you open them!
    Only open files from people you trust, or if you know what you're doing.", "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", "gui_mode_receive_button": "Receive Files", -- cgit v1.2.3-54-g00ecf From 691db6343d46e75f63957a5cb08374254fedbb7e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 21:54:28 -0700 Subject: Make ShareMode and ReceiveMode inherit from the same class, Mode --- onionshare_gui/mode.py | 67 +++++++++++++++++++++++++++++++++ onionshare_gui/onionshare_gui.py | 4 +- onionshare_gui/receive_mode/__init__.py | 33 ++++------------ onionshare_gui/share_mode/__init__.py | 41 +++++++------------- 4 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 onionshare_gui/mode.py diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py new file mode 100644 index 00000000..639ef274 --- /dev/null +++ b/onionshare_gui/mode.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from .server_status import ServerStatus + +class Mode(QtWidgets.QWidget): + """ + The class that ShareMode and ReceiveMode inherit from. + """ + def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray, filenames=None): + super(Mode, self).__init__() + self.common = common + self.qtapp = qtapp + self.app = app + self.web = web + + self.status_bar = status_bar + self.server_share_status_label = server_share_status_label + self.system_tray = system_tray + + self.filenames = filenames + + # Server status + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) + + # Primary action layout + self.primary_action_layout = QtWidgets.QVBoxLayout() + self.primary_action_layout.addWidget(self.server_status) + self.primary_action = QtWidgets.QWidget() + self.primary_action.setLayout(self.primary_action_layout) + + # Layout + self.layout = QtWidgets.QVBoxLayout() + self.layout.addWidget(self.primary_action) + self.setLayout(self.layout) + + def init(self): + """ + Add custom initialization of the mode here. + """ + pass + + def timer_callback(self): + """ + This method is called regularly on a timer. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 7e4ec41d..f976013f 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -157,7 +157,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.insertWidget(0, self.server_share_status_label) # Share and receive mode widgets - self.share_mode = ShareMode(self.common, filenames, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) + self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray, filenames) + self.share_mode.init() self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) self.share_mode.start_server_finished.connect(self.update_server_status_indicator) @@ -169,6 +170,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.set_share_server_active.connect(self.set_share_server_active) self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) + self.receive_mode.init() self.update_mode_switcher() self.update_server_status_indicator() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 4c74a60a..b25b728f 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -21,42 +21,23 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ..server_status import ServerStatus +from ..mode import Mode -class ReceiveMode(QtWidgets.QWidget): +class ReceiveMode(Mode): """ Parts of the main window UI for receiving files. """ - def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray): - super(ReceiveMode, self).__init__() - self.common = common - self.qtapp = qtapp - self.app = app - self.web = web - - self.status_bar = status_bar - self.server_share_status_label = server_share_status_label - self.system_tray = system_tray - + def init(self): + """ + Custom initialization for ReceiveMode. + """ # Receive mode info self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) self.receive_info.setMinimumHeight(80) self.receive_info.setWordWrap(True) - # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) - - # Primary action layout - primary_action_layout = QtWidgets.QVBoxLayout() - primary_action_layout.addWidget(self.server_status) - self.primary_action = QtWidgets.QWidget() - self.primary_action.setLayout(primary_action_layout) - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.receive_info) - layout.addWidget(self.primary_action) - self.setLayout(layout) + self.layout.insertWidget(0, self.receive_info) def timer_callback(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 3a7add92..d26db929 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -28,12 +28,12 @@ from onionshare.onion import * from .file_selection import FileSelection from .downloads import Downloads -from ..server_status import ServerStatus +from ..mode import Mode from ..onion_thread import OnionThread from ..widgets import Alert -class ShareMode(QtWidgets.QWidget): +class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ @@ -44,27 +44,19 @@ class ShareMode(QtWidgets.QWidget): starting_server_error = QtCore.pyqtSignal(str) set_share_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, filenames, qtapp, app, web, status_bar, server_share_status_label, system_tray): - super(ShareMode, self).__init__() - self.common = common - self.qtapp = qtapp - self.app = app - self.web = web - - self.status_bar = status_bar - self.server_share_status_label = server_share_status_label - self.system_tray = system_tray - + def init(self): + """ + Custom initialization for ReceiveMode. + """ # File selection self.file_selection = FileSelection(self.common) - if filenames: - for filename in filenames: + if self.filenames: + for filename in self.filenames: self.file_selection.file_list.add_file(filename) - + # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, True, self.file_selection) - self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_started.connect(self.start_server) + self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_stopped.connect(self.update_primary_action) @@ -122,11 +114,7 @@ class ShareMode(QtWidgets.QWidget): self.info_widget.hide() # Primary action layout - primary_action_layout = QtWidgets.QVBoxLayout() - primary_action_layout.addWidget(self.server_status) - primary_action_layout.addWidget(self.filesize_warning) - self.primary_action = QtWidgets.QWidget() - self.primary_action.setLayout(primary_action_layout) + self.primary_action_layout.addWidget(self.filesize_warning) self.primary_action.hide() self.update_primary_action() @@ -134,11 +122,8 @@ class ShareMode(QtWidgets.QWidget): self._zip_progress_bar = None # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.info_widget) - layout.addLayout(self.file_selection) - layout.addWidget(self.primary_action) - self.setLayout(layout) + self.layout.insertWidget(1, self.info_widget) + self.layout.insertLayout(0, self.file_selection) # Always start with focus on file selection self.file_selection.setFocus() -- cgit v1.2.3-54-g00ecf From 4a1995ef559f7834e25c8300c101304f15ec4c2e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 22:14:23 -0700 Subject: Move a lot of logic from ShareMode into generic Mode --- onionshare_gui/mode.py | 250 +++++++++++++++++++++++++++++++++ onionshare_gui/share_mode/__init__.py | 251 +--------------------------------- 2 files changed, 251 insertions(+), 250 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 639ef274..e4269dc6 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -17,16 +17,29 @@ 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 . """ +import threading +import time +import os from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from onionshare.common import Common, ShutdownTimer from .server_status import ServerStatus +from .onion_thread import OnionThread +from .widgets import Alert class Mode(QtWidgets.QWidget): """ The class that ShareMode and ReceiveMode inherit from. """ + start_server_finished = QtCore.pyqtSignal() + stop_server_finished = QtCore.pyqtSignal() + starting_server_step2 = QtCore.pyqtSignal() + starting_server_step3 = QtCore.pyqtSignal() + starting_server_error = QtCore.pyqtSignal(str) + set_share_server_active = QtCore.pyqtSignal(bool) + def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray, filenames=None): super(Mode, self).__init__() self.common = common @@ -42,6 +55,14 @@ class Mode(QtWidgets.QWidget): # Server status self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) + self.server_status.server_started.connect(self.start_server) + self.server_status.server_stopped.connect(self.stop_server) + self.server_status.server_canceled.connect(self.cancel_server) + self.start_server_finished.connect(self.server_status.start_server_finished) + self.stop_server_finished.connect(self.server_status.stop_server_finished) + self.starting_server_step2.connect(self.start_server_step2) + self.starting_server_step3.connect(self.start_server_step3) + self.starting_server_error.connect(self.start_server_error) # Primary action layout self.primary_action_layout = QtWidgets.QVBoxLayout() @@ -65,3 +86,232 @@ class Mode(QtWidgets.QWidget): This method is called regularly on a timer. """ pass + + def start_server(self): + """ + Start the onionshare server. This uses multiple threads to start the Tor onion + server and the web app. + """ + self.common.log('ShareMode', 'start_server') + + self.set_share_server_active.emit(True) + + self.app.set_stealth(self.common.settings.get('use_stealth')) + + # Hide and reset the downloads if we have previously shared + self.downloads.reset_downloads() + self.reset_info_counters() + self.status_bar.clearMessage() + self.server_share_status_label.setText('') + + # Reset web counters + self.web.download_count = 0 + self.web.error404_count = 0 + + # start the onion service in a new thread + def start_onion_service(self): + try: + self.app.start_onion_service() + self.starting_server_step2.emit() + + except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + self.starting_server_error.emit(e.args[0]) + return + + + self.app.stay_open = not self.common.settings.get('close_after_first_download') + + # start onionshare http service in new thread + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) + t.daemon = True + t.start() + # wait for modules in thread to load, preventing a thread-related cx_Freeze crash + time.sleep(0.2) + + self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) + self.t.daemon = True + self.t.start() + + def start_server_step2(self): + """ + Step 2 in starting the onionshare server. Zipping up files. + """ + self.common.log('ShareMode', 'start_server_step2') + + # add progress bar to the status bar, indicating the compressing of files. + self._zip_progress_bar = ZipProgressBar(0) + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + self._zip_progress_bar.total_files_size = Mode._compute_total_size(self.filenames) + self.status_bar.insertWidget(0, self._zip_progress_bar) + + # prepare the files for sending in a new thread + def finish_starting_server(self): + # prepare files to share + def _set_processed_size(x): + if self._zip_progress_bar != None: + self._zip_progress_bar.update_processed_size_signal.emit(x) + try: + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) + + # Only continue if the server hasn't been canceled + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.starting_server_step3.emit() + self.start_server_finished.emit() + except OSError as e: + self.starting_server_error.emit(e.strerror) + return + + t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) + t.daemon = True + t.start() + + def start_server_step3(self): + """ + Step 3 in starting the onionshare server. This displays the large filesize + warning, if applicable. + """ + self.common.log('ShareMode', 'start_server_step3') + + # Remove zip progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + # warn about sending large files over Tor + if self.web.zip_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + if self.common.settings.get('shutdown_timeout'): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_status.timeout) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) + self.app.shutdown_timer.start() + # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. + else: + self.stop_server() + self.start_server_error(strings._('gui_server_started_after_timeout')) + + def start_server_error(self, error): + """ + If there's an error when trying to start the onion service + """ + self.common.log('ShareMode', 'start_server_error') + + self.set_share_server_active.emit(False) + + Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.server_status.stop_server() + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + self.status_bar.clearMessage() + + def cancel_server(self): + """ + Cancel the server while it is preparing to start + """ + if self.t: + self.t.quit() + self.stop_server() + + def stop_server(self): + """ + Stop the onionshare server. + """ + self.common.log('ShareMode', 'stop_server') + + if self.server_status.status != self.server_status.STATUS_STOPPED: + try: + self.web.stop(self.app.port) + except: + # Probably we had no port to begin with (Onion service didn't start) + pass + self.app.cleanup() + + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + self.filesize_warning.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.update_downloads_in_progress(0) + self.file_selection.file_list.adjustSize() + + self.set_share_server_active.emit(False) + self.stop_server_finished.emit() + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size + + +class ZipProgressBar(QtWidgets.QProgressBar): + update_processed_size_signal = QtCore.pyqtSignal(int) + + def __init__(self, total_files_size): + super(ZipProgressBar, self).__init__() + self.setMaximumHeight(20) + self.setMinimumWidth(200) + self.setValue(0) + self.setFormat(strings._('zip_progress_bar_format')) + cssStyleData =""" + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + } + + QProgressBar::chunk { + border: 0px; + background-color: #4e064f; + width: 10px; + }""" + self.setStyleSheet(cssStyleData) + + self._total_files_size = total_files_size + self._processed_size = 0 + + self.update_processed_size_signal.connect(self.update_processed_size) + + @property + def total_files_size(self): + return self._total_files_size + + @total_files_size.setter + def total_files_size(self, val): + self._total_files_size = val + + @property + def processed_size(self): + return self._processed_size + + @processed_size.setter + def processed_size(self, val): + self.update_processed_size(val) + + def update_processed_size(self, val): + self._processed_size = val + if self.processed_size < self.total_files_size: + self.setValue(int((self.processed_size * 100) / self.total_files_size)) + elif self.total_files_size != 0: + self.setValue(100) + else: + self.setValue(0) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index d26db929..699e469b 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -17,19 +17,14 @@ 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 . """ -import os -import threading -import time from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from onionshare.common import Common, ShutdownTimer from onionshare.onion import * from .file_selection import FileSelection from .downloads import Downloads from ..mode import Mode -from ..onion_thread import OnionThread from ..widgets import Alert @@ -37,13 +32,6 @@ class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ - start_server_finished = QtCore.pyqtSignal() - stop_server_finished = QtCore.pyqtSignal() - starting_server_step2 = QtCore.pyqtSignal() - starting_server_step3 = QtCore.pyqtSignal() - starting_server_error = QtCore.pyqtSignal(str) - set_share_server_active = QtCore.pyqtSignal(bool) - def init(self): """ Custom initialization for ReceiveMode. @@ -55,21 +43,13 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.server_started.connect(self.start_server) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) - self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.cancel_server) self.server_status.server_canceled.connect(self.file_selection.server_stopped) self.server_status.server_canceled.connect(self.update_primary_action) - self.start_server_finished.connect(self.server_status.start_server_finished) - self.stop_server_finished.connect(self.server_status.stop_server_finished) self.file_selection.file_list.files_updated.connect(self.server_status.update) self.file_selection.file_list.files_updated.connect(self.update_primary_action) - self.starting_server_step2.connect(self.start_server_step2) - self.starting_server_step3.connect(self.start_server_step3) - self.starting_server_error.connect(self.start_server_error) # Filesize warning self.filesize_warning = QtWidgets.QLabel() @@ -263,170 +243,6 @@ class ShareMode(Mode): # Resize window self.adjustSize() - def start_server(self): - """ - Start the onionshare server. This uses multiple threads to start the Tor onion - server and the web app. - """ - self.common.log('ShareMode', 'start_server') - - self.set_share_server_active.emit(True) - - self.app.set_stealth(self.common.settings.get('use_stealth')) - - # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() - self.reset_info_counters() - self.status_bar.clearMessage() - self.server_share_status_label.setText('') - - # Reset web counters - self.web.download_count = 0 - self.web.error404_count = 0 - - # start the onion service in a new thread - def start_onion_service(self): - try: - self.app.start_onion_service() - self.starting_server_step2.emit() - - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: - self.starting_server_error.emit(e.args[0]) - return - - - self.app.stay_open = not self.common.settings.get('close_after_first_download') - - # start onionshare http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) - t.daemon = True - t.start() - # wait for modules in thread to load, preventing a thread-related cx_Freeze crash - time.sleep(0.2) - - self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') - self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) - self.t.daemon = True - self.t.start() - - def start_server_step2(self): - """ - Step 2 in starting the onionshare server. Zipping up files. - """ - self.common.log('ShareMode', 'start_server_step2') - - # add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(0) - self.filenames = [] - for index in range(self.file_selection.file_list.count()): - self.filenames.append(self.file_selection.file_list.item(index).filename) - - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) - - # prepare the files for sending in a new thread - def finish_starting_server(self): - # prepare files to share - def _set_processed_size(x): - if self._zip_progress_bar != None: - self._zip_progress_bar.update_processed_size_signal.emit(x) - try: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - - # Only continue if the server hasn't been canceled - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.starting_server_step3.emit() - self.start_server_finished.emit() - except OSError as e: - self.starting_server_error.emit(e.strerror) - return - - t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) - t.daemon = True - t.start() - - def start_server_step3(self): - """ - Step 3 in starting the onionshare server. This displays the large filesize - warning, if applicable. - """ - self.common.log('ShareMode', 'start_server_step3') - - # Remove zip progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - # warn about sending large files over Tor - if self.web.zip_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() - - if self.common.settings.get('shutdown_timeout'): - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_status.timeout) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) - self.app.shutdown_timer.start() - # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. - else: - self.stop_server() - self.start_server_error(strings._('gui_server_started_after_timeout')) - - def start_server_error(self, error): - """ - If there's an error when trying to start the onion service - """ - self.common.log('ShareMode', 'start_server_error') - - self.set_share_server_active.emit(False) - - Alert(self.common, error, QtWidgets.QMessageBox.Warning) - self.server_status.stop_server() - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - self.status_bar.clearMessage() - - def cancel_server(self): - """ - Cancel the server while it is preparing to start - """ - if self.t: - self.t.quit() - self.stop_server() - - def stop_server(self): - """ - Stop the onionshare server. - """ - self.common.log('ShareMode', 'stop_server') - - if self.server_status.status != self.server_status.STATUS_STOPPED: - try: - self.web.stop(self.app.port) - except: - # Probably we had no port to begin with (Onion service didn't start) - pass - self.app.cleanup() - - # Remove the progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.update_downloads_in_progress(0) - self.file_selection.file_list.adjustSize() - - self.set_share_server_active.emit(False) - self.stop_server_finished.emit() - def downloads_toggled(self, checked): """ When the 'Show/hide downloads' button is toggled, show or hide the downloads window. @@ -468,69 +284,4 @@ class ShareMode(Mode): self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) - - @staticmethod - def _compute_total_size(filenames): - total_size = 0 - for filename in filenames: - if os.path.isfile(filename): - total_size += os.path.getsize(filename) - if os.path.isdir(filename): - total_size += Common.dir_size(filename) - return total_size - - -class ZipProgressBar(QtWidgets.QProgressBar): - update_processed_size_signal = QtCore.pyqtSignal(int) - - def __init__(self, total_files_size): - super(ZipProgressBar, self).__init__() - self.setMaximumHeight(20) - self.setMinimumWidth(200) - self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - } - - QProgressBar::chunk { - border: 0px; - background-color: #4e064f; - width: 10px; - }""" - self.setStyleSheet(cssStyleData) - - self._total_files_size = total_files_size - self._processed_size = 0 - - self.update_processed_size_signal.connect(self.update_processed_size) - - @property - def total_files_size(self): - return self._total_files_size - - @total_files_size.setter - def total_files_size(self, val): - self._total_files_size = val - - @property - def processed_size(self): - return self._processed_size - - @processed_size.setter - def processed_size(self, val): - self.update_processed_size(val) - - def update_processed_size(self, val): - self._processed_size = val - if self.processed_size < self.total_files_size: - self.setValue(int((self.processed_size * 100) / self.total_files_size)) - elif self.total_files_size != 0: - self.setValue(100) - else: - self.setValue(0) + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 4c6b37988924f1273dd14ce4197148cc1b4a40d9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 22:59:26 -0700 Subject: Split out customization of Mode into _custom() functions, and implement those customizations in ShareMode --- onionshare_gui/mode.py | 218 +++++++++++--------------------- onionshare_gui/onionshare_gui.py | 12 +- onionshare_gui/receive_mode/__init__.py | 7 + onionshare_gui/server_status.py | 15 ++- onionshare_gui/share_mode/__init__.py | 167 ++++++++++++++++++++++-- 5 files changed, 256 insertions(+), 163 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index e4269dc6..debd2657 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -17,13 +17,12 @@ 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 . """ -import threading import time -import os +import threading from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from onionshare.common import Common, ShutdownTimer +from onionshare.common import ShutdownTimer from .server_status import ServerStatus from .onion_thread import OnionThread @@ -38,9 +37,9 @@ class Mode(QtWidgets.QWidget): starting_server_step2 = QtCore.pyqtSignal() starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) - set_share_server_active = QtCore.pyqtSignal(bool) + set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, web, status_bar, server_share_status_label, system_tray, filenames=None): + def __init__(self, common, qtapp, app, web, status_bar, server_status_label, system_tray, filenames=None): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -48,13 +47,13 @@ class Mode(QtWidgets.QWidget): self.web = web self.status_bar = status_bar - self.server_share_status_label = server_share_status_label + self.server_status_label = server_status_label self.system_tray = system_tray self.filenames = filenames # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, False) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -77,7 +76,7 @@ class Mode(QtWidgets.QWidget): def init(self): """ - Add custom initialization of the mode here. + Add custom initialization here. """ pass @@ -92,17 +91,16 @@ class Mode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('ShareMode', 'start_server') - - self.set_share_server_active.emit(True) + self.common.log('Mode', 'start_server') + + self.start_server_custom() + self.set_server_active.emit(True) self.app.set_stealth(self.common.settings.get('use_stealth')) - # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() - self.reset_info_counters() + # Clear the status bar self.status_bar.clearMessage() - self.server_share_status_label.setText('') + self.server_status_label.setText('') # Reset web counters self.web.download_count = 0 @@ -128,64 +126,44 @@ class Mode(QtWidgets.QWidget): # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) - self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread') + self.common.log('Mode', 'start_server', 'Starting an onion thread') self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() + + def start_server_custom(self): + """ + Add custom initialization here. + """ + pass def start_server_step2(self): """ - Step 2 in starting the onionshare server. Zipping up files. + Step 2 in starting the onionshare server. """ - self.common.log('ShareMode', 'start_server_step2') - - # add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(0) - self.filenames = [] - for index in range(self.file_selection.file_list.count()): - self.filenames.append(self.file_selection.file_list.item(index).filename) - - self._zip_progress_bar.total_files_size = Mode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) - - # prepare the files for sending in a new thread - def finish_starting_server(self): - # prepare files to share - def _set_processed_size(x): - if self._zip_progress_bar != None: - self._zip_progress_bar.update_processed_size_signal.emit(x) - try: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - - # Only continue if the server hasn't been canceled - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.starting_server_step3.emit() - self.start_server_finished.emit() - except OSError as e: - self.starting_server_error.emit(e.strerror) - return + self.common.log('Mode', 'start_server_step2') - t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) - t.daemon = True - t.start() + self.start_server_step2_custom() - def start_server_step3(self): + # Nothing to do here. + + # start_server_step2_custom has call these to move on: + # self.starting_server_step3.emit() + # self.start_server_finished.emit() + + def start_server_step2_custom(self): """ - Step 3 in starting the onionshare server. This displays the large filesize - warning, if applicable. + Add custom initialization here. """ - self.common.log('ShareMode', 'start_server_step3') + pass - # Remove zip progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None + def start_server_step3(self): + """ + Step 3 in starting the onionshare server. + """ + self.common.log('Mode', 'start_server_step3') - # warn about sending large files over Tor - if self.web.zip_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() + self.start_server_step3_custom() if self.common.settings.get('shutdown_timeout'): # Convert the date value to seconds between now and then @@ -200,21 +178,31 @@ class Mode(QtWidgets.QWidget): self.stop_server() self.start_server_error(strings._('gui_server_started_after_timeout')) + def start_server_step3_custom(self): + """ + Add custom initialization here. + """ + pass + def start_server_error(self, error): """ If there's an error when trying to start the onion service """ - self.common.log('ShareMode', 'start_server_error') - - self.set_share_server_active.emit(False) + self.common.log('Mode', 'start_server_error') Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.set_server_active.emit(False) self.server_status.stop_server() - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None self.status_bar.clearMessage() + self.start_server_error_custom() + + def start_server_error_custom(self): + """ + Add custom initialization here. + """ + pass + def cancel_server(self): """ Cancel the server while it is preparing to start @@ -227,7 +215,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('ShareMode', 'stop_server') + self.common.log('Mode', 'stop_server') if self.server_status.status != self.server_status.STATUS_STOPPED: try: @@ -237,81 +225,27 @@ class Mode(QtWidgets.QWidget): pass self.app.cleanup() - # Remove the progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.update_downloads_in_progress(0) - self.file_selection.file_list.adjustSize() + self.stop_server_custom() - self.set_share_server_active.emit(False) + self.set_server_active.emit(False) self.stop_server_finished.emit() + + def stop_server_custom(self): + """ + Add custom initialization here. + """ + pass - @staticmethod - def _compute_total_size(filenames): - total_size = 0 - for filename in filenames: - if os.path.isfile(filename): - total_size += os.path.getsize(filename) - if os.path.isdir(filename): - total_size += Common.dir_size(filename) - return total_size - - -class ZipProgressBar(QtWidgets.QProgressBar): - update_processed_size_signal = QtCore.pyqtSignal(int) - - def __init__(self, total_files_size): - super(ZipProgressBar, self).__init__() - self.setMaximumHeight(20) - self.setMinimumWidth(200) - self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - } - - QProgressBar::chunk { - border: 0px; - background-color: #4e064f; - width: 10px; - }""" - self.setStyleSheet(cssStyleData) - - self._total_files_size = total_files_size - self._processed_size = 0 - - self.update_processed_size_signal.connect(self.update_processed_size) - - @property - def total_files_size(self): - return self._total_files_size - - @total_files_size.setter - def total_files_size(self, val): - self._total_files_size = val - - @property - def processed_size(self): - return self._processed_size - - @processed_size.setter - def processed_size(self, val): - self.update_processed_size(val) - - def update_processed_size(self, val): - self._processed_size = val - if self.processed_size < self.total_files_size: - self.setValue(int((self.processed_size * 100) / self.total_files_size)) - elif self.total_files_size != 0: - self.setValue(100) - else: - self.setValue(0) + def handle_tor_broke(self): + """ + Handle connection from Tor breaking. + """ + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.server_status.stop_server() + self.handle_tor_broke_custom() + + def handle_tor_broke_custom(self): + """ + Add custom initialization here. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f976013f..1aa38c1c 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -152,12 +152,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setStatusBar(self.status_bar) # Status bar, sharing messages - self.server_share_status_label = QtWidgets.QLabel('') - self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') - self.status_bar.insertWidget(0, self.server_share_status_label) + self.server_status_label = QtWidgets.QLabel('') + self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') + self.status_bar.insertWidget(0, self.server_status_label) # Share and receive mode widgets - self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray, filenames) + self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray, filenames) self.share_mode.init() self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) @@ -168,8 +168,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) - self.share_mode.set_share_server_active.connect(self.set_share_server_active) - self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_share_status_label, self.system_tray) + self.share_mode.set_server_active.connect(self.set_share_server_active) + self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray) self.receive_mode.init() self.update_mode_switcher() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index b25b728f..b7813170 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -44,3 +44,10 @@ class ReceiveMode(Mode): This method is called regularly on a timer while receive mode is active. """ pass + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Nothing to do here but move on to step 3. + """ + self.starting_server_step3.emit() + self.start_server_finished.emit() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 54f54582..602af595 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -39,22 +39,18 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, share_mode, file_selection=None): + def __init__(self, common, qtapp, app, web, file_selection=None): super(ServerStatus, self).__init__() self.common = common self.status = self.STATUS_STOPPED + self.share_mode = False # Gets changed in in self.set_share_mode self.qtapp = qtapp self.app = app self.web = web - # Only used in share mode - self.share_mode = share_mode - if self.share_mode: - self.file_selection = file_selection - # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -118,6 +114,13 @@ class ServerStatus(QtWidgets.QWidget): self.setLayout(layout) self.update() + + def set_share_mode(self, file_selection): + """ + The server status is in share mode. + """ + self.share_mode = True + self.file_selection = file_selection def shutdown_timeout_reset(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 699e469b..0d05da06 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -17,10 +17,13 @@ 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 . """ +import threading +import os from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.onion import * +from onionshare.common import Common from .file_selection import FileSelection from .downloads import Downloads @@ -43,6 +46,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status + self.server_status.set_share_mode(self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -102,8 +106,8 @@ class ShareMode(Mode): self._zip_progress_bar = None # Layout - self.layout.insertWidget(1, self.info_widget) self.layout.insertLayout(0, self.file_selection) + self.layout.insertWidget(0, self.info_widget) # Always start with focus on file selection self.file_selection.setFocus() @@ -129,18 +133,98 @@ class ShareMode(Mode): if self.web.download_count == 0 or self.web.done: self.server_status.stop_server() self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('close_on_timeout', True)) + self.server_status_label.setText(strings._('close_on_timeout', True)) # A download is probably still running - hold off on stopping the share else: self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('timeout_download_still_running', True)) + self.server_status_label.setText(strings._('timeout_download_still_running', True)) - def handle_tor_broke(self): + def start_server_custom(self): """ - Handle connection from Tor breaking. + Starting the server. + """ + # Hide and reset the downloads if we have previously shared + self.downloads.reset_downloads() + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + # Add progress bar to the status bar, indicating the compressing of files. + self._zip_progress_bar = ZipProgressBar(0) + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self.status_bar.insertWidget(0, self._zip_progress_bar) + + # Prepare the files for sending in a new thread + def finish_starting_server(self): + # Prepare files to share + def _set_processed_size(x): + if self._zip_progress_bar != None: + self._zip_progress_bar.update_processed_size_signal.emit(x) + + try: + self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) + self.app.cleanup_filenames.append(self.web.zip_filename) + + # Only continue if the server hasn't been canceled + if self.server_status.status != self.server_status.STATUS_STOPPED: + self.starting_server_step3.emit() + self.start_server_finished.emit() + except OSError as e: + self.starting_server_error.emit(e.strerror) + return + + t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) + t.daemon = True + t.start() + + def start_server_step3_custom(self): + """ + Step 3 in starting the server. Remove zip progess bar, and display large filesize + warning, if applicable. + """ + # Remove zip progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + # Warn about sending large files over Tor + if self.web.zip_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + def start_server_error_custom(self): + """ + Start server error. + """ + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + def stop_server_custom(self): + """ + Stop server. + """ + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + self.filesize_warning.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.update_downloads_in_progress(0) + self.file_selection.file_list.adjustSize() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. """ - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.server_status.stop_server() self.primary_action.hide() self.info_widget.hide() @@ -190,7 +274,7 @@ class ShareMode(Mode): if not self.web.stay_open: self.server_status.stop_server() self.status_bar.clearMessage() - self.server_share_status_label.setText(strings._('closing_automatically', True)) + self.server_status_label.setText(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) @@ -284,4 +368,69 @@ class ShareMode(Mode): self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) \ No newline at end of file + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size + + +class ZipProgressBar(QtWidgets.QProgressBar): + update_processed_size_signal = QtCore.pyqtSignal(int) + + def __init__(self, total_files_size): + super(ZipProgressBar, self).__init__() + self.setMaximumHeight(20) + self.setMinimumWidth(200) + self.setValue(0) + self.setFormat(strings._('zip_progress_bar_format')) + cssStyleData =""" + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + } + + QProgressBar::chunk { + border: 0px; + background-color: #4e064f; + width: 10px; + }""" + self.setStyleSheet(cssStyleData) + + self._total_files_size = total_files_size + self._processed_size = 0 + + self.update_processed_size_signal.connect(self.update_processed_size) + + @property + def total_files_size(self): + return self._total_files_size + + @total_files_size.setter + def total_files_size(self, val): + self._total_files_size = val + + @property + def processed_size(self): + return self._processed_size + + @processed_size.setter + def processed_size(self, val): + self.update_processed_size(val) + + def update_processed_size(self, val): + self._processed_size = val + if self.processed_size < self.total_files_size: + self.setValue(int((self.processed_size * 100) / self.total_files_size)) + elif self.total_files_size != 0: + self.setValue(100) + else: + self.setValue(0) -- cgit v1.2.3-54-g00ecf From 4050977899d0fac3c2e6894745371b23a245e626 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 23:03:57 -0700 Subject: When you start receive mode, it now runs OnionShareGui.set_server_active, to hide the appropriate mode switchers buttons --- onionshare_gui/onionshare_gui.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 1aa38c1c..c30142ff 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -168,9 +168,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.button_clicked.connect(self.clear_message) self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) - self.share_mode.set_server_active.connect(self.set_share_server_active) + self.share_mode.set_server_active.connect(self.set_server_active) self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray) self.receive_mode.init() + self.receive_mode.set_server_active.connect(self.set_server_active) self.update_mode_switcher() self.update_server_status_indicator() @@ -191,8 +192,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() - # The server isn't active yet - self.set_share_server_active(False) + # The servers isn't active yet + self.set_server_active(False) # Create the timer self.timer = QtCore.QTimer() @@ -424,14 +425,18 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.status_bar.clearMessage() - def set_share_server_active(self, active): + def set_server_active(self, active): """ Disable the Settings and Receive Files buttons while an Share Files server is active. """ if active: self.settings_button.hide() - self.share_mode_button.show() - self.receive_mode_button.hide() + if self.mode == self.MODE_SHARE: + self.share_mode_button.show() + self.receive_mode_button.hide() + else: + self.share_mode_button.hide() + self.receive_mode_button.show() else: self.settings_button.show() self.share_mode_button.show() -- cgit v1.2.3-54-g00ecf From 9e9f65572ba06b43c916b1a43eb46f42b88fcfe8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 09:30:53 -0700 Subject: Instead of creating a Web object and passing it into OnionShareGui, now each mode creates its own separate Web object, instantiated in its own way --- onionshare/__init__.py | 3 +- onionshare/web.py | 11 +++---- onionshare_gui/__init__.py | 6 +--- onionshare_gui/mode.py | 19 +++++------ onionshare_gui/onionshare_gui.py | 57 ++++++++++++++++++--------------- onionshare_gui/receive_mode/__init__.py | 13 +++++++- onionshare_gui/server_status.py | 8 ++--- onionshare_gui/share_mode/__init__.py | 11 +++++++ 8 files changed, 73 insertions(+), 55 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 27d9506f..6ff322d2 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -105,7 +105,8 @@ def main(cwd=None): sys.exit() # Create the Web object - web = Web(common, stay_open, False, receive) + web = Web(common, False, receive) + web.stay_open = stay_open # Start the Onion object onion = Onion(common) diff --git a/onionshare/web.py b/onionshare/web.py index bc8699b9..42f4b6d8 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -38,28 +38,25 @@ from flask import ( ) from werkzeug.utils import secure_filename -from . import strings, common +from . import strings class Web(object): """ The Web object is the OnionShare web server, powered by flask """ - def __init__(self, common, stay_open, gui_mode, receive_mode=False): + def __init__(self, common, gui_mode, receive_mode=False): self.common = common # The flask app self.app = Flask(__name__, - static_folder=common.get_resource_path('static'), - template_folder=common.get_resource_path('templates')) + static_folder=self.common.get_resource_path('static'), + template_folder=self.common.get_resource_path('templates')) self.app.secret_key = self.common.random_string(8) # Debug mode? if self.common.debug: self.debug_mode() - # Stay open after the first download? - self.stay_open = stay_open - # Are we running in GUI mode? self.gui_mode = gui_mode diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 38db94d4..a46fe009 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -24,7 +24,6 @@ from PyQt5 import QtCore, QtWidgets from onionshare import strings from onionshare.common import Common -from onionshare.web import Web from onionshare.onion import Onion from onionshare.onionshare import OnionShare @@ -100,9 +99,6 @@ def main(): if not valid: sys.exit() - # Create the Web object - web = Web(common, stay_open, True) - # Start the Onion onion = Onion(common) @@ -110,7 +106,7 @@ def main(): app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) # Launch the gui - gui = OnionShareGui(common, web, onion, qtapp, app, filenames, config, local_only) + gui = OnionShareGui(common, onion, qtapp, app, filenames, config, local_only) # Clean up when app quits def shutdown(): diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index debd2657..90d1ec7b 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -39,12 +39,11 @@ class Mode(QtWidgets.QWidget): starting_server_error = QtCore.pyqtSignal(str) set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, web, status_bar, server_status_label, system_tray, filenames=None): + def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None): super(Mode, self).__init__() self.common = common self.qtapp = qtapp self.app = app - self.web = web self.status_bar = status_bar self.server_status_label = server_status_label @@ -52,8 +51,11 @@ class Mode(QtWidgets.QWidget): self.filenames = filenames + # The web object gets created in init() + self.web = None + # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web) + self.server_status = ServerStatus(self.common, self.qtapp, self.app) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -102,11 +104,7 @@ class Mode(QtWidgets.QWidget): self.status_bar.clearMessage() self.server_status_label.setText('') - # Reset web counters - self.web.download_count = 0 - self.web.error404_count = 0 - - # start the onion service in a new thread + # Start the onion service in a new thread def start_onion_service(self): try: self.app.start_onion_service() @@ -116,14 +114,13 @@ class Mode(QtWidgets.QWidget): self.starting_server_error.emit(e.args[0]) return - self.app.stay_open = not self.common.settings.get('close_after_first_download') - # start onionshare http service in new thread + # Start http service in new thread t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) t.daemon = True t.start() - # wait for modules in thread to load, preventing a thread-related cx_Freeze crash + # Wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) self.common.log('Mode', 'start_server', 'Starting an onion thread') diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index c30142ff..c53aebb7 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -21,6 +21,7 @@ import queue from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from onionshare.web import Web from .share_mode import ShareMode from .receive_mode import ReceiveMode @@ -39,19 +40,19 @@ class OnionShareGui(QtWidgets.QMainWindow): MODE_SHARE = 'share' MODE_RECEIVE = 'receive' - def __init__(self, common, web, onion, qtapp, app, filenames, config=False, local_only=False): + def __init__(self, common, onion, qtapp, app, filenames, config=False, local_only=False): super(OnionShareGui, self).__init__() self.common = common self.common.log('OnionShareGui', '__init__') - self.web = web self.onion = onion self.qtapp = qtapp self.app = app self.local_only = local_only self.mode = self.MODE_SHARE + self.web = None self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) @@ -156,8 +157,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') self.status_bar.insertWidget(0, self.server_status_label) - # Share and receive mode widgets - self.share_mode = ShareMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray, filenames) + # Share mode + self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) self.share_mode.init() self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) @@ -169,7 +170,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.set_server_active.connect(self.set_server_active) - self.receive_mode = ReceiveMode(self.common, qtapp, app, web, self.status_bar, self.server_status_label, self.system_tray) + + # Receive mode + self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray) self.receive_mode.init() self.receive_mode.set_server_active.connect(self.set_server_active) @@ -371,34 +374,36 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.handle_tor_broke() - events = [] + # If we have a web object, process events from it + if self.web: + events = [] - done = False - while not done: - try: - r = self.web.q.get(False) - events.append(r) - except queue.Empty: - done = True + done = False + while not done: + try: + r = self.web.q.get(False) + events.append(r) + except queue.Empty: + done = True - for event in events: - if event["type"] == self.web.REQUEST_LOAD: - self.share_mode.handle_request_load(event) + for event in events: + if event["type"] == Web.REQUEST_LOAD: + self.share_mode.handle_request_load(event) - elif event["type"] == self.web.REQUEST_DOWNLOAD: - self.share_mode.handle_request_download(event) + elif event["type"] == Web.REQUEST_DOWNLOAD: + self.share_mode.handle_request_download(event) - elif event["type"] == self.web.REQUEST_RATE_LIMIT: - self.share_mode.handle_request_rate_limit(event) + elif event["type"] == Web.REQUEST_RATE_LIMIT: + self.share_mode.handle_request_rate_limit(event) - elif event["type"] == self.web.REQUEST_PROGRESS: - self.share_mode.handle_request_progress(event) + elif event["type"] == Web.REQUEST_PROGRESS: + self.share_mode.handle_request_progress(event) - elif event["type"] == self.web.REQUEST_CANCELED: - self.share_mode.handle_request_canceled(event) + elif event["type"] == Web.REQUEST_CANCELED: + self.share_mode.handle_request_canceled(event) - elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) + elif event["path"] != '/favicon.ico': + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) if self.mode == self.MODE_SHARE: self.share_mode.timer_callback() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index b7813170..76e8b30b 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -20,6 +20,7 @@ along with this program. If not, see . from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from onionshare.web import Web from ..mode import Mode @@ -31,6 +32,13 @@ class ReceiveMode(Mode): """ Custom initialization for ReceiveMode. """ + # Create the Web object + self.web = Web(self.common, True, True) + + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + # Receive mode info self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) self.receive_info.setMinimumHeight(80) @@ -47,7 +55,10 @@ class ReceiveMode(Mode): def start_server_step2_custom(self): """ - Step 2 in starting the server. Nothing to do here but move on to step 3. + Step 2 in starting the server. """ + # Set up web + + # Continue self.starting_server_step3.emit() self.start_server_finished.emit() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 602af595..2e087eb5 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -39,7 +39,7 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, file_selection=None): + def __init__(self, common, qtapp, app, file_selection=None): super(ServerStatus, self).__init__() self.common = common @@ -49,7 +49,8 @@ class ServerStatus(QtWidgets.QWidget): self.qtapp = qtapp self.app = app - self.web = web + + self.web = None # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) @@ -112,8 +113,6 @@ class ServerStatus(QtWidgets.QWidget): layout.addLayout(url_layout) layout.addWidget(self.shutdown_timeout_container) self.setLayout(layout) - - self.update() def set_share_mode(self, file_selection): """ @@ -121,6 +120,7 @@ class ServerStatus(QtWidgets.QWidget): """ self.share_mode = True self.file_selection = file_selection + self.update() def shutdown_timeout_reset(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 0d05da06..c905a80d 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -24,6 +24,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.onion import * from onionshare.common import Common +from onionshare.web import Web from .file_selection import FileSelection from .downloads import Downloads @@ -39,6 +40,9 @@ class ShareMode(Mode): """ Custom initialization for ReceiveMode. """ + # Create the Web object + self.web = Web(self.common, True, False) + # File selection self.file_selection = FileSelection(self.common) if self.filenames: @@ -54,6 +58,9 @@ class ShareMode(Mode): self.server_status.server_canceled.connect(self.update_primary_action) self.file_selection.file_list.files_updated.connect(self.server_status.update) self.file_selection.file_list.files_updated.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() # Filesize warning self.filesize_warning = QtWidgets.QLabel() @@ -143,6 +150,10 @@ class ShareMode(Mode): """ Starting the server. """ + # Reset web counters + self.web.download_count = 0 + self.web.error404_count = 0 + # Hide and reset the downloads if we have previously shared self.downloads.reset_downloads() self.reset_info_counters() -- cgit v1.2.3-54-g00ecf From ff55d7df75cc5f61326debca9691c7f5ba56d0fd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 10:59:38 -0700 Subject: Make OnionShareGui use the proper web object --- onionshare_gui/onionshare_gui.py | 51 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index c53aebb7..8b4dbc83 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -52,7 +52,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.local_only = local_only self.mode = self.MODE_SHARE - self.web = None self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) @@ -374,36 +373,40 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.handle_tor_broke() - # If we have a web object, process events from it - if self.web: - events = [] + # Process events from the web object + if self.mode == self.MODE_SHARE: + web = self.share_mode.web + else: + web = self.receive_mode.web + + events = [] - done = False - while not done: - try: - r = self.web.q.get(False) - events.append(r) - except queue.Empty: - done = True + done = False + while not done: + try: + r = web.q.get(False) + events.append(r) + except queue.Empty: + done = True - for event in events: - if event["type"] == Web.REQUEST_LOAD: - self.share_mode.handle_request_load(event) + for event in events: + if event["type"] == Web.REQUEST_LOAD: + self.share_mode.handle_request_load(event) - elif event["type"] == Web.REQUEST_DOWNLOAD: - self.share_mode.handle_request_download(event) + elif event["type"] == Web.REQUEST_DOWNLOAD: + self.share_mode.handle_request_download(event) - elif event["type"] == Web.REQUEST_RATE_LIMIT: - self.share_mode.handle_request_rate_limit(event) + elif event["type"] == Web.REQUEST_RATE_LIMIT: + self.share_mode.handle_request_rate_limit(event) - elif event["type"] == Web.REQUEST_PROGRESS: - self.share_mode.handle_request_progress(event) + elif event["type"] == Web.REQUEST_PROGRESS: + self.share_mode.handle_request_progress(event) - elif event["type"] == Web.REQUEST_CANCELED: - self.share_mode.handle_request_canceled(event) + elif event["type"] == Web.REQUEST_CANCELED: + self.share_mode.handle_request_canceled(event) - elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"])) + elif event["path"] != '/favicon.ico': + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) if self.mode == self.MODE_SHARE: self.share_mode.timer_callback() -- cgit v1.2.3-54-g00ecf From c6a2cab529c173ff249786e8dc4495ddc55ba5aa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 11:00:59 -0700 Subject: Make Web's the REQUEST_ constants static attributes --- onionshare/web.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 42f4b6d8..d49450cd 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -44,6 +44,13 @@ class Web(object): """ The Web object is the OnionShare web server, powered by flask """ + REQUEST_LOAD = 0 + REQUEST_DOWNLOAD = 1 + REQUEST_PROGRESS = 2 + REQUEST_OTHER = 3 + REQUEST_CANCELED = 4 + REQUEST_RATE_LIMIT = 5 + def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -90,12 +97,6 @@ class Web(object): ('Server', 'OnionShare') ] - self.REQUEST_LOAD = 0 - self.REQUEST_DOWNLOAD = 1 - self.REQUEST_PROGRESS = 2 - self.REQUEST_OTHER = 3 - self.REQUEST_CANCELED = 4 - self.REQUEST_RATE_LIMIT = 5 self.q = queue.Queue() self.slug = None -- cgit v1.2.3-54-g00ecf From 87d93c097f1cf2a4e64c38e8213c4142d28b0c33 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 22:20:12 -0700 Subject: Fix server status indicator --- onionshare_gui/onionshare_gui.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8b4dbc83..47bcf69a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -128,8 +128,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png')) self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) - self.server_status_label = QtWidgets.QLabel() - self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }') + self.server_status_label = QtWidgets.QLabel('') + self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) @@ -151,11 +151,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) - # Status bar, sharing messages - self.server_status_label = QtWidgets.QLabel('') - self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') - self.status_bar.insertWidget(0, self.server_status_label) - # Share mode self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) self.share_mode.init() -- cgit v1.2.3-54-g00ecf From 2e4db9eb31df5bb4ca47863e2316d622a6f3f2aa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 22:32:20 -0700 Subject: Connect the right signals and slots for recieve mode, and now the receive mode server starts --- onionshare_gui/onionshare_gui.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 47bcf69a..f3f7150a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -168,6 +168,16 @@ class OnionShareGui(QtWidgets.QMainWindow): # Receive mode self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray) self.receive_mode.init() + self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator) + self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator) + self.receive_mode.start_server_finished.connect(self.update_server_status_indicator) + self.receive_mode.stop_server_finished.connect(self.update_server_status_indicator) + self.receive_mode.stop_server_finished.connect(self.stop_server_finished) + self.receive_mode.start_server_finished.connect(self.clear_message) + self.receive_mode.server_status.button_clicked.connect(self.clear_message) + self.receive_mode.server_status.url_copied.connect(self.copy_url) + self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) + self.receive_mode.set_server_active.connect(self.set_server_active) self.receive_mode.set_server_active.connect(self.set_server_active) self.update_mode_switcher() -- cgit v1.2.3-54-g00ecf From 0996e8c0647afc564867ab77607282eda41c1dc4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 23:02:04 -0700 Subject: Change the URL description in receive mode --- onionshare_gui/server_status.py | 35 +++++++++++++++++++++++------------ onionshare_gui/share_mode/__init__.py | 3 +-- share/locale/en.json | 3 ++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 2e087eb5..4a0fba40 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -35,6 +35,9 @@ class ServerStatus(QtWidgets.QWidget): url_copied = QtCore.pyqtSignal() hidservauth_copied = QtCore.pyqtSignal() + MODE_SHARE = 'share' + MODE_RECEIVE = 'receive' + STATUS_STOPPED = 0 STATUS_WORKING = 1 STATUS_STARTED = 2 @@ -45,7 +48,7 @@ class ServerStatus(QtWidgets.QWidget): self.common = common self.status = self.STATUS_STOPPED - self.share_mode = False # Gets changed in in self.set_share_mode + self.mode = None # Gets set in self.set_mode self.qtapp = qtapp self.app = app @@ -78,7 +81,7 @@ class ServerStatus(QtWidgets.QWidget): # URL layout url_font = QtGui.QFont() - self.url_description = QtWidgets.QLabel(strings._('gui_url_description', True)) + self.url_description = QtWidgets.QLabel() self.url_description.setWordWrap(True) self.url_description.setMinimumHeight(50) self.url = QtWidgets.QLabel() @@ -114,12 +117,15 @@ class ServerStatus(QtWidgets.QWidget): layout.addWidget(self.shutdown_timeout_container) self.setLayout(layout) - def set_share_mode(self, file_selection): + def set_mode(self, share_mode, file_selection=None): """ The server status is in share mode. """ - self.share_mode = True - self.file_selection = file_selection + self.mode = share_mode + + if self.mode == ServerStatus.MODE_SHARE: + self.file_selection = file_selection + self.update() def shutdown_timeout_reset(self): @@ -138,15 +144,20 @@ class ServerStatus(QtWidgets.QWidget): self.url_description.show() info_image = self.common.get_resource_path('images/info.png') - self.url_description.setText(strings._('gui_url_description', True).format(info_image)) + + if self.mode == ServerStatus.MODE_SHARE: + self.url_description.setText(strings._('gui_share_url_description', True).format(info_image)) + else: + self.url_description.setText(strings._('gui_receive_url_description', True).format(info_image)) + # Show a Tool Tip explaining the lifecycle of this URL if self.common.settings.get('save_private_key'): - if 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)) else: self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) else: - if 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)) else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) @@ -179,7 +190,7 @@ class ServerStatus(QtWidgets.QWidget): button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - if self.share_mode and self.file_selection.get_num_files() == 0: + if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: self.server_button.hide() else: self.server_button.show() @@ -187,7 +198,7 @@ class ServerStatus(QtWidgets.QWidget): if self.status == self.STATUS_STOPPED: self.server_button.setStyleSheet(button_stopped_style) self.server_button.setEnabled(True) - if self.share_mode: + if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_start_server', True)) else: self.server_button.setText(strings._('gui_receive_start_server', True)) @@ -197,13 +208,13 @@ class ServerStatus(QtWidgets.QWidget): elif self.status == self.STATUS_STARTED: self.server_button.setStyleSheet(button_started_style) self.server_button.setEnabled(True) - if self.share_mode: + if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_stop_server', True)) else: self.server_button.setText(strings._('gui_share_stop_server', True)) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() - if self.share_mode: + if self.mode == ServerStatus.MODE_SHARE: self.server_button.setToolTip(strings._('gui_share_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) else: self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index c905a80d..3f319864 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -31,7 +31,6 @@ from .downloads import Downloads from ..mode import Mode from ..widgets import Alert - class ShareMode(Mode): """ Parts of the main window UI for sharing files. @@ -50,7 +49,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_share_mode(self.file_selection) + self.server_status.set_mode('share', self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) diff --git a/share/locale/en.json b/share/locale/en.json index 4b1ebb09..faa9400c 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -150,7 +150,8 @@ "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", - "gui_url_description": "Anyone with this link can download your files using the Tor Browser: ", + "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", + "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", "gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.", "gui_url_label_onetime": "This share will expire after the first download", -- cgit v1.2.3-54-g00ecf From 406515085e4f55c48230584c1fa21a6128b2619e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 23:19:46 -0700 Subject: Fixed crash when starting recieve mode server --- onionshare/web.py | 1 + onionshare_gui/receive_mode/__init__.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index d49450cd..04be5b66 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -453,6 +453,7 @@ class Web(object): """ Start the flask web server. """ + self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug)) self.generate_slug(persistent_slug) self.stay_open = stay_open diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 76e8b30b..290eb029 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -34,6 +34,11 @@ class ReceiveMode(Mode): """ # Create the Web object self.web = Web(self.common, True, True) + + # Server status + self.server_status.set_mode('receive') + #self.server_status.server_stopped.connect(self.update_primary_action) + #self.server_status.server_canceled.connect(self.update_primary_action) # Tell server_status about web, then update self.server_status.web = self.web @@ -53,12 +58,21 @@ class ReceiveMode(Mode): """ pass + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.error404_count = 0 + + # Hide and reset the downloads if we have previously shared + #self.downloads.reset_downloads() + #self.reset_info_counters() + def start_server_step2_custom(self): """ Step 2 in starting the server. """ - # Set up web - # Continue self.starting_server_step3.emit() self.start_server_finished.emit() -- cgit v1.2.3-54-g00ecf From 74a799f0c11adcc96041db18019960d21e8d74e6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 12:03:10 -0700 Subject: Work in progress commit, moving the timer_callback logic from ShareMode into Mode so ReceiveMode can use it as well --- onionshare_gui/mode.py | 41 +++++++++++++++++++++++++++++++-- onionshare_gui/onionshare_gui.py | 9 ++++---- onionshare_gui/receive_mode/__init__.py | 13 +++++++++++ onionshare_gui/server_status.py | 2 +- onionshare_gui/share_mode/__init__.py | 40 +++++++++++++++++--------------- 5 files changed, 79 insertions(+), 26 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 90d1ec7b..a1f475d1 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -86,6 +86,43 @@ class Mode(QtWidgets.QWidget): """ This method is called regularly on a timer. """ + self.common.log('Mode', 'timer_callback') + # If the auto-shutdown timer has stopped, stop the server + print(self.server_status.status) ## HERE IS THE PROBLEM, self.server_status.status isn't getting updated + if self.server_status.status == ServerStatus.STATUS_STARTED: + print('debug1') + if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): + print('debug2') + if self.timeout > 0: + print('debug3') + now = QtCore.QDateTime.currentDateTime() + seconds_remaining = now.secsTo(self.server_status.timeout) + + # Update the server button + server_button_text = self.get_stop_server_shutdown_timeout_text() + self.server_status.server_button.setText(server_button_text.format(seconds_remaining)) + + self.status_bar.clearMessage() + if not self.app.shutdown_timer.is_alive(): + if self.timeout_finished_should_stop_server(): + self.server_status.stop_server() + + def timer_callback_custom(self): + """ + Add custom timer code. + """ + pass + + def get_stop_server_shutdown_timeout_text(self): + """ + Return the string to put on the stop server button, if there's a shutdown timeout + """ + pass + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ pass def start_server(self): @@ -214,7 +251,7 @@ class Mode(QtWidgets.QWidget): """ self.common.log('Mode', 'stop_server') - if self.server_status.status != self.server_status.STATUS_STOPPED: + if self.server_status.status != ServerStatus.STATUS_STOPPED: try: self.web.stop(self.app.port) except: @@ -237,7 +274,7 @@ class Mode(QtWidgets.QWidget): """ Handle connection from Tor breaking. """ - if self.server_status.status != self.server_status.STATUS_STOPPED: + if self.server_status.status != ServerStatus.STATUS_STOPPED: self.server_status.stop_server() self.handle_tor_broke_custom() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f3f7150a..15f8a514 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -341,6 +341,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # If we switched off the shutdown timeout setting, ensure the widget is hidden. if not self.common.settings.get('shutdown_timeout'): self.share_mode.server_status.shutdown_timeout_container.hide() + self.receive_mode.server_status.shutdown_timeout_container.hide() d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only) d.settings_saved.connect(reload_settings) @@ -348,6 +349,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # When settings close, refresh the server status UI self.share_mode.server_status.update() + self.receive_mode.server_status.update() def check_for_updates(self): """ @@ -367,6 +369,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for messages communicated from the web app, and update the GUI accordingly. Also, call ShareMode and ReceiveMode's timer_callbacks. """ + self.common.log('OnionShareGui', 'timer_callback') self.update() if not self.local_only: @@ -413,10 +416,8 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) - if self.mode == self.MODE_SHARE: - self.share_mode.timer_callback() - else: - self.receive_mode.timer_callback() + self.share_mode.timer_callback() + self.receive_mode.timer_callback() def copy_url(self): """ diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 290eb029..43457566 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -58,6 +58,19 @@ class ReceiveMode(Mode): """ pass + def get_stop_server_shutdown_timeout_text(self): + """ + 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) + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + # TODO: wait until the final upload is done before stoppign the server? + return True + def start_server_custom(self): """ Starting the server. diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 4a0fba40..bbc3a450 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -211,7 +211,7 @@ class ServerStatus(QtWidgets.QWidget): if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_stop_server', True)) else: - self.server_button.setText(strings._('gui_share_stop_server', True)) + self.server_button.setText(strings._('gui_receive_stop_server', True)) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() if self.mode == ServerStatus.MODE_SHARE: diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 3f319864..15ba2f18 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -118,7 +118,7 @@ class ShareMode(Mode): # Always start with focus on file selection self.file_selection.setFocus() - def timer_callback(self): + def timer_callback_custom(self): """ This method is called regularly on a timer while share mode is active. """ @@ -126,24 +126,26 @@ class ShareMode(Mode): if self.new_download: self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) self.new_download = False - - # If the auto-shutdown timer has stopped, stop the server - if self.server_status.status == self.server_status.STATUS_STARTED: - if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): - if self.timeout > 0: - now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.timeout) - self.server_status.server_button.setText(strings._('gui_share_stop_server_shutdown_timeout', True).format(seconds_remaining)) - if not self.app.shutdown_timer.is_alive(): - # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.download_count == 0 or self.web.done: - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_status_label.setText(strings._('close_on_timeout', True)) - # A download is probably still running - hold off on stopping the share - else: - self.status_bar.clearMessage() - self.server_status_label.setText(strings._('timeout_download_still_running', True)) + + def get_stop_server_shutdown_timeout_text(self): + """ + 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) + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + # If there were no attempts to download the share, or all downloads are done, we can stop + if self.web.download_count == 0 or self.web.done: + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_timeout', True)) + return True + # A download is probably still running - hold off on stopping the share + else: + self.server_status_label.setText(strings._('timeout_download_still_running', True)) + return False def start_server_custom(self): """ -- cgit v1.2.3-54-g00ecf From c1413ad7da71b1c271657c0f442d770887c5ae30 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:41:15 -0700 Subject: ReceiveMode was overloading timer_callback instead of timer_callback_custom --- onionshare_gui/mode.py | 5 ----- onionshare_gui/receive_mode/__init__.py | 11 +++++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index a1f475d1..8a16e773 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -86,15 +86,10 @@ class Mode(QtWidgets.QWidget): """ This method is called regularly on a timer. """ - self.common.log('Mode', 'timer_callback') # If the auto-shutdown timer has stopped, stop the server - print(self.server_status.status) ## HERE IS THE PROBLEM, self.server_status.status isn't getting updated if self.server_status.status == ServerStatus.STATUS_STARTED: - print('debug1') if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): - print('debug2') if self.timeout > 0: - print('debug3') now = QtCore.QDateTime.currentDateTime() seconds_remaining = now.secsTo(self.server_status.timeout) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 43457566..2b7a6295 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -51,12 +51,15 @@ class ReceiveMode(Mode): # Layout self.layout.insertWidget(0, self.receive_info) - - def timer_callback(self): + + def timer_callback_custom(self): """ - This method is called regularly on a timer while receive mode is active. + This method is called regularly on a timer while share mode is active. """ - pass + # Scroll to the bottom of the download progress bar log pane if a new download has been added + #if self.new_download: + # self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) + # self.new_download = False def get_stop_server_shutdown_timeout_text(self): """ -- cgit v1.2.3-54-g00ecf From 5d037a78fa38334d2ff6ba13f2cbd13ecb16429f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:48:31 -0700 Subject: Remove a log line that prints each timer_callback that I missed --- onionshare_gui/onionshare_gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 15f8a514..c671e145 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -369,7 +369,6 @@ class OnionShareGui(QtWidgets.QMainWindow): Check for messages communicated from the web app, and update the GUI accordingly. Also, call ShareMode and ReceiveMode's timer_callbacks. """ - self.common.log('OnionShareGui', 'timer_callback') self.update() if not self.local_only: -- cgit v1.2.3-54-g00ecf From 1456361566750b9e926de8e48b5128bb85267c4e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:59:36 -0700 Subject: Generalize the handling of Tor exceptions, more logging in Web --- onionshare/__init__.py | 4 ++-- onionshare/web.py | 8 +++++--- onionshare_gui/mode.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 6ff322d2..be498bc6 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -112,11 +112,11 @@ def main(cwd=None): onion = Onion(common) try: onion.connect(custom_settings=False, config=config) - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorNotSupported, BundledTorTimeout) as e: - sys.exit(e.args[0]) except KeyboardInterrupt: print("") sys.exit() + except Exception as e: + sys.exit(e.args[0]) # Start the onionshare app try: diff --git a/onionshare/web.py b/onionshare/web.py index 04be5b66..2f418839 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -417,11 +417,13 @@ class Web(object): 'data': data }) - def generate_slug(self, persistent_slug=''): - if persistent_slug: + def generate_slug(self, persistent_slug=None): + self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) + if persistent_slug != None: self.slug = persistent_slug else: self.slug = self.common.build_slug() + self.common.log('Web', 'generate_slug', 'slug is set to {}'.format(self.slug)) def debug_mode(self): """ @@ -449,7 +451,7 @@ class Web(object): raise RuntimeError('Not running with the Werkzeug Server') func() - def start(self, port, stay_open=False, persistent_slug=''): + def start(self, port, stay_open=False, persistent_slug=None): """ Start the flask web server. """ diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 8a16e773..2d3f37bd 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -142,7 +142,7 @@ class Mode(QtWidgets.QWidget): self.app.start_onion_service() self.starting_server_step2.emit() - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + except Exception as e: self.starting_server_error.emit(e.args[0]) return -- cgit v1.2.3-54-g00ecf From 1a4aaa70fa7549520da837fcfdc9f63e0e38c3f8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 15:00:23 -0700 Subject: Fix a race condition where the URL was sometimes getting copied to the clipboard before it was actually generated, causing a crash --- onionshare/__init__.py | 1 + onionshare/onionshare.py | 17 ++++++++++++----- onionshare/web.py | 5 +++-- onionshare_gui/mode.py | 20 +++++++++++++------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index be498bc6..05c50884 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -122,6 +122,7 @@ def main(cwd=None): try: app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) app.set_stealth(stealth) + app.choose_port() app.start_onion_service() except KeyboardInterrupt: print("") diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index ad5ea113..e7306510 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -38,6 +38,7 @@ class OnionShare(object): self.hidserv_dir = None self.onion_host = None + self.port = None self.stealth = None # files and dirs to delete on shutdown @@ -59,6 +60,15 @@ class OnionShare(object): self.stealth = stealth self.onion.stealth = stealth + + def choose_port(self): + """ + Choose a random port. + """ + try: + self.port = self.common.get_available_port(17600, 17650) + except: + raise OSError(strings._('no_available_port')) def start_onion_service(self): """ @@ -66,11 +76,8 @@ class OnionShare(object): """ self.common.log('OnionShare', 'start_onion_service') - # Choose a random port - try: - self.port = self.common.get_available_port(17600, 17650) - except: - raise OSError(strings._('no_available_port')) + if not self.port: + self.choose_port() if self.local_only: self.onion_host = '127.0.0.1:{0:d}'.format(self.port) diff --git a/onionshare/web.py b/onionshare/web.py index 2f418839..79c49e39 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -419,11 +419,12 @@ class Web(object): def generate_slug(self, persistent_slug=None): self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) - if persistent_slug != None: + if persistent_slug != None and persistent_slug != '': self.slug = persistent_slug + self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) else: self.slug = self.common.build_slug() - self.common.log('Web', 'generate_slug', 'slug is set to {}'.format(self.slug)) + self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) def debug_mode(self): """ diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 2d3f37bd..9ff0ee76 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -138,6 +138,19 @@ class Mode(QtWidgets.QWidget): # Start the onion service in a new thread def start_onion_service(self): + # Choose a port for the web app + self.app.choose_port() + + # Start http service in new thread + t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) + t.daemon = True + t.start() + + # Wait for the web app slug to generate before continuing + while self.web.slug == None: + time.sleep(0.1) + + # Now start the onion service try: self.app.start_onion_service() self.starting_server_step2.emit() @@ -148,13 +161,6 @@ class Mode(QtWidgets.QWidget): self.app.stay_open = not self.common.settings.get('close_after_first_download') - # Start http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) - t.daemon = True - t.start() - # Wait for modules in thread to load, preventing a thread-related cx_Freeze crash - time.sleep(0.2) - self.common.log('Mode', 'start_server', 'Starting an onion thread') self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True -- cgit v1.2.3-54-g00ecf From 2a7f6e0d5a1d1415a2c9b076f5ff53d5463eb6c0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 15:23:57 -0700 Subject: Make the quit warning work in receive mode, and use a different warning string --- onionshare_gui/onionshare_gui.py | 11 +++++++++-- share/locale/cs.json | 2 +- share/locale/da.json | 2 +- share/locale/en.json | 3 ++- share/locale/eo.json | 2 +- share/locale/nl.json | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index c671e145..f223ff3c 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -461,11 +461,18 @@ class OnionShareGui(QtWidgets.QMainWindow): def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') try: - if self.server_status.status != self.server_status.STATUS_STOPPED: + if self.mode == OnionShareGui.MODE_SHARE: + server_status = self.share_mode.server_status + else: + server_status = self.receive_mode.server_status + if server_status.status != server_status.STATUS_STOPPED: self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() dialog.setWindowTitle(strings._('gui_quit_title', True)) - dialog.setText(strings._('gui_quit_warning', True)) + if self.mode == OnionShareGui.MODE_SHARE: + dialog.setText(strings._('gui_share_quit_warning', True)) + else: + dialog.setText(strings._('gui_receive_quit_warning', True)) dialog.setIcon(QtWidgets.QMessageBox.Critical) quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) diff --git a/share/locale/cs.json b/share/locale/cs.json index 5c40bcdc..233a156b 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -43,7 +43,7 @@ "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", + "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", "gui_quit_warning_dont_quit": "Zůstat", "error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.", diff --git a/share/locale/da.json b/share/locale/da.json index be4b462f..12db70ae 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -56,7 +56,7 @@ "gui_download_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", "gui_download_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", + "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Afslut ikke", "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.", diff --git a/share/locale/en.json b/share/locale/en.json index faa9400c..89ab8e6b 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -70,7 +70,8 @@ "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Transfer in Progress", - "gui_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", + "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", + "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?", "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", diff --git a/share/locale/eo.json b/share/locale/eo.json index fb037a87..90b7e9c7 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -43,7 +43,7 @@ "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.", + "gui_share_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.", "gui_quit_warning_quit": "Foriri", "gui_quit_warning_dont_quit": "Ne foriri", "error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.", diff --git a/share/locale/nl.json b/share/locale/nl.json index 4c5cfe76..062635d2 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -54,7 +54,7 @@ "gui_download_progress_starting": "{0:s}, %p% (ETA berekenen)", "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", + "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Niet afsluiten", "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.", -- cgit v1.2.3-54-g00ecf From 6632a4b426aaaef0eb7ecf01a58106ead5388e52 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 21:08:53 -0700 Subject: Add two new receive mode settings: receive_allow_receiver_shutdown and receive_public_mode --- onionshare/settings.py | 4 +++- onionshare_gui/settings_dialog.py | 26 ++++++++++++++++++++++++++ share/locale/en.json | 4 +++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index c3311d60..6d551ca0 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -72,7 +72,9 @@ class Settings(object): 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': self.build_default_downloads_dir() + 'downloads_dir': self.build_default_downloads_dir(), + 'receive_allow_receiver_shutdown': True, + 'receive_public_mode': False } self._settings = {} self.fill_in_defaults() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index dac8d75d..da5be7fc 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -88,9 +88,21 @@ class SettingsDialog(QtWidgets.QDialog): downloads_layout.addWidget(self.downloads_dir_lineedit) downloads_layout.addWidget(downloads_button) + # Allow the receiver to shutdown the server + self.receive_allow_receiver_shutdown_checkbox = QtWidgets.QCheckBox() + 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)) + + # Use a slug + self.receive_public_mode_checkbox = QtWidgets.QCheckBox() + self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) + self.receive_public_mode_checkbox.setText(strings._("gui_settings_receive_public_mode_checkbox", True)) + # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout.addLayout(downloads_layout) + receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox) + receiving_group_layout.addWidget(self.receive_public_mode_checkbox) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group.setLayout(receiving_group_layout) @@ -413,6 +425,18 @@ class SettingsDialog(QtWidgets.QDialog): downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) + receive_allow_receiver_shutdown = self.old_settings.get('receive_allow_receiver_shutdown') + if receive_allow_receiver_shutdown: + self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked) + + receive_public_mode = self.old_settings.get('receive_public_mode') + if receive_public_mode: + self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) + else: + self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) + use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) @@ -794,6 +818,8 @@ class SettingsDialog(QtWidgets.QDialog): # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') settings.set('downloads_dir', self.downloads_dir_lineedit.text()) + settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) + settings.set('receive_public_mode', self.receive_public_mode_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): diff --git a/share/locale/en.json b/share/locale/en.json index 89ab8e6b..d6f325bb 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -177,5 +177,7 @@ "gui_mode_receive_button": "Receive Files", "gui_settings_receiving_label": "Receiving options", "gui_settings_downloads_label": "Save files to", - "gui_settings_downloads_button": "Browse" + "gui_settings_downloads_button": "Browse", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Allow people who upload files to you to stop Receive Mode for you", + "gui_settings_receive_public_mode_checkbox": "Receive Mode is open to the public\n(don't try to prevent people from guessing the OnionShare address)" } -- cgit v1.2.3-54-g00ecf From 996df24646803fde0b21bf0b32646141bf2c5c2d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 15:34:11 -0700 Subject: Make receive_allow_receiver_shutdown setting work --- onionshare/web.py | 13 +++++++++---- share/templates/receive.html | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 79c49e39..762e2342 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -266,7 +266,8 @@ class Web(object): r = make_response(render_template( 'receive.html', - slug=self.slug)) + slug=self.slug, + receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) return self.add_security_headers(r) @self.app.route("//upload", methods=['POST']) @@ -326,9 +327,13 @@ class Web(object): @self.app.route("//close", methods=['POST']) def close(slug_candidate): self.check_slug_candidate(slug_candidate) - self.force_shutdown() - r = make_response(render_template('closed.html')) - return self.add_security_headers(r) + + if self.common.settings.get('receive_allow_receiver_shutdown'): + self.force_shutdown() + r = make_response(render_template('closed.html')) + return self.add_security_headers(r) + else: + return redirect('/{}'.format(slug_candidate)) def common_routes(self): """ diff --git a/share/templates/receive.html b/share/templates/receive.html index d1ec3b3a..7cc4319f 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -34,10 +34,10 @@
    - + {% if receive_allow_receiver_shutdown %}
    - + {% endif %} -- cgit v1.2.3-54-g00ecf From 6cfb7026da8c3dc8be0f966e710068f60f6dd998 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 15:49:18 -0700 Subject: Display desktop notification to the user when the receiver closes the server, and finish up closing the server --- onionshare/web.py | 26 ++++++++++++++-------- onionshare_gui/mode.py | 38 +++++++++++++++++++++++++++++++++ onionshare_gui/onionshare_gui.py | 24 +++++++++++---------- onionshare_gui/receive_mode/__init__.py | 7 ++++++ share/locale/en.json | 4 +++- 5 files changed, 78 insertions(+), 21 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 762e2342..f0d8f532 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -50,6 +50,7 @@ class Web(object): REQUEST_OTHER = 3 REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 5 + REQUEST_CLOSE_SERVER = 6 def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -118,6 +119,9 @@ class Web(object): # shutting down the server only works within the context of flask, so the easiest way to do it is over http self.shutdown_slug = self.common.random_string(16) + # Keep track if the server is running + self.running = False + # Define the ewb app routes self.common_routes() if self.receive_mode: @@ -331,6 +335,7 @@ class Web(object): if self.common.settings.get('receive_allow_receiver_shutdown'): self.force_shutdown() r = make_response(render_template('closed.html')) + self.add_request(self.REQUEST_CLOSE_SERVER, request.path) return self.add_security_headers(r) else: return redirect('/{}'.format(slug_candidate)) @@ -451,11 +456,12 @@ class Web(object): """ Stop the flask web server, from the context of the flask app. """ - # shutdown the flask service + # Shutdown the flask service func = request.environ.get('werkzeug.server.shutdown') if func is None: raise RuntimeError('Not running with the Werkzeug Server') func() + self.running = False def start(self, port, stay_open=False, persistent_slug=None): """ @@ -472,6 +478,7 @@ class Web(object): else: host = '127.0.0.1' + self.running = True self.app.run(host=host, port=port, threaded=True) def stop(self, port): @@ -483,16 +490,17 @@ class Web(object): # serving the file self.client_cancel = True - # to stop flask, load http://127.0.0.1://shutdown - try: - s = socket.socket() - s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) - except: + # To stop flask, load http://127.0.0.1://shutdown + if self.running: try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() + s = socket.socket() + s.connect(('127.0.0.1', port)) + s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) except: - pass + try: + urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() + except: + pass class ZipWriter(object): diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 9ff0ee76..cca2254d 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -284,3 +284,41 @@ class Mode(QtWidgets.QWidget): Add custom initialization here. """ pass + + # Handle web server events + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + pass + + def handle_request_download(self, event): + """ + Handle REQUEST_DOWNLOAD event. + """ + pass + + def handle_request_rate_limit(self, event): + """ + Handle REQUEST_RATE_LIMIT event. + """ + pass + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + pass + + def handle_request_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + pass + + def handle_request_close_server(self, event): + """ + Handle REQUEST_CLOSE_SERVER event. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f223ff3c..f0820ac4 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -382,41 +382,43 @@ class OnionShareGui(QtWidgets.QMainWindow): # Process events from the web object if self.mode == self.MODE_SHARE: - web = self.share_mode.web + mode = self.share_mode else: - web = self.receive_mode.web + mode = self.receive_mode events = [] done = False while not done: try: - r = web.q.get(False) + r = mode.web.q.get(False) events.append(r) except queue.Empty: done = True for event in events: if event["type"] == Web.REQUEST_LOAD: - self.share_mode.handle_request_load(event) + mode.handle_request_load(event) elif event["type"] == Web.REQUEST_DOWNLOAD: - self.share_mode.handle_request_download(event) + mode.handle_request_download(event) elif event["type"] == Web.REQUEST_RATE_LIMIT: - self.share_mode.handle_request_rate_limit(event) + mode.handle_request_rate_limit(event) elif event["type"] == Web.REQUEST_PROGRESS: - self.share_mode.handle_request_progress(event) + mode.handle_request_progress(event) elif event["type"] == Web.REQUEST_CANCELED: - self.share_mode.handle_request_canceled(event) + mode.handle_request_canceled(event) + + elif event["type"] == Web.REQUEST_CLOSE_SERVER: + mode.handle_request_close_server(event) elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(web.error404_count, strings._('other_page_loaded', True), event["path"])) + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) - self.share_mode.timer_callback() - self.receive_mode.timer_callback() + mode.timer_callback() def copy_url(self): """ diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 2b7a6295..5b65a052 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -92,3 +92,10 @@ class ReceiveMode(Mode): # Continue self.starting_server_step3.emit() self.start_server_finished.emit() + + def handle_request_close_server(self, event): + """ + Handle REQUEST_CLOSE_SERVER event. + """ + self.stop_server() + self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) diff --git a/share/locale/en.json b/share/locale/en.json index d6f325bb..08678747 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -179,5 +179,7 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Allow people who upload files to you to stop Receive Mode for you", - "gui_settings_receive_public_mode_checkbox": "Receive Mode is open to the public\n(don't try to prevent people from guessing the OnionShare address)" + "gui_settings_receive_public_mode_checkbox": "Receive Mode is open to the public\n(don't try to prevent people from guessing the OnionShare address)", + "systray_close_server_title": "OnionShare Server Closed", + "systray_close_server_message": "The user closed the server" } -- cgit v1.2.3-54-g00ecf From 4f89082f185d2f2132911ef24700f4d616208037 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:33:48 -0700 Subject: Add support for receive mode's "public mode", which doesn't use a slug. Still needs more testing --- onionshare/web.py | 72 +++++++++++++++++++++++++++++------------ onionshare_gui/server_status.py | 16 ++++++--- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index f0d8f532..d11924b5 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -264,20 +264,27 @@ class Web(object): """ The web app routes for sharing files """ - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) + def index_logic(): r = make_response(render_template( 'receive.html', slug=self.slug, receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) return self.add_security_headers(r) - - @self.app.route("//upload", methods=['POST']) - def upload(slug_candidate): + + @self.app.route("/") + def index(slug_candidate): self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('receive_public_mode'): + return self.error404() + return index_logic() + + def upload_logic(slug_candidate=''): files = request.files.getlist('file[]') filenames = [] for f in files: @@ -328,10 +335,19 @@ class Web(object): return redirect('/{}'.format(slug_candidate)) - @self.app.route("//close", methods=['POST']) - def close(slug_candidate): + @self.app.route("//upload", methods=['POST']) + def upload(slug_candidate): self.check_slug_candidate(slug_candidate) - + return upload_logic(slug_candidate) + + @self.app.route("/upload") + def upload_public(): + if not self.common.settings.get('receive_public_mode'): + return self.error404() + return upload_logic() + + + def close_logic(slug_candidate=''): if self.common.settings.get('receive_allow_receiver_shutdown'): self.force_shutdown() r = make_response(render_template('closed.html')) @@ -339,6 +355,17 @@ class Web(object): return self.add_security_headers(r) else: return redirect('/{}'.format(slug_candidate)) + + @self.app.route("//close", methods=['POST']) + def close(slug_candidate): + self.check_slug_candidate(slug_candidate) + return close_logic(slug_candidate) + + @self.app.route("/upload") + def close_public(): + if not self.common.settings.get('receive_public_mode'): + return self.error404() + return close_logic() def common_routes(self): """ @@ -349,17 +376,7 @@ class Web(object): """ 404 error page. """ - self.add_request(self.REQUEST_OTHER, request.path) - - if request.path != '/favicon.ico': - self.error404_count += 1 - if self.error404_count == 20: - self.add_request(self.REQUEST_RATE_LIMIT, request.path) - self.force_shutdown() - print(strings._('error_rate_limit')) - - r = make_response(render_template('404.html'), 404) - return self.add_security_headers(r) + return self.error404() @self.app.route("//shutdown") def shutdown(slug_candidate): @@ -370,6 +387,21 @@ class Web(object): self.force_shutdown() return "" + def error404(self): + self.add_request(self.REQUEST_OTHER, request.path) + if request.path != '/favicon.ico': + self.error404_count += 1 + + # In receive mode, with public mode enabled, skip rate limiting 404s + if not (self.receive_mode and self.common.settings.get('receive_public_mode')): + if self.error404_count == 20: + self.add_request(self.REQUEST_RATE_LIMIT, request.path) + self.force_shutdown() + print(strings._('error_rate_limit')) + + r = make_response(render_template('404.html'), 404) + return self.add_security_headers(r) + def add_security_headers(self, r): """ Add security headers to a request diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index bbc3a450..c0a7172f 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -162,7 +162,7 @@ class ServerStatus(QtWidgets.QWidget): else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) + self.url.setText(self.get_url()) self.url.show() self.copy_url_button.show() @@ -299,10 +299,8 @@ class ServerStatus(QtWidgets.QWidget): """ Copy the onionshare URL to the clipboard. """ - url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) - clipboard = self.qtapp.clipboard() - clipboard.setText(url) + clipboard.setText(self.get_url()) self.url_copied.emit() @@ -314,3 +312,13 @@ class ServerStatus(QtWidgets.QWidget): clipboard.setText(self.app.auth_string) self.hidservauth_copied.emit() + + def get_url(self): + """ + Returns the OnionShare URL. + """ + if self.mode == ServerStatus.MODE_RECEIVE and self.common.settings.get('receive_public_mode'): + url = 'http://{0:s}'.format(self.app.onion_host) + else: + url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) + return url -- cgit v1.2.3-54-g00ecf From 9a076635c5eccf74f9843f7ca4d92c9ad933049c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:41:05 -0700 Subject: Make the "download page loaded" and "upload page loaded" messages displayed as systray notifications instead of in the status bar, and make it work for Receive Mode --- onionshare/web.py | 2 ++ onionshare_gui/mode.py | 3 ++- onionshare_gui/receive_mode/__init__.py | 6 ++++++ onionshare_gui/share_mode/__init__.py | 9 +-------- share/locale/en.json | 6 ++++-- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index d11924b5..8d80810b 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -266,6 +266,8 @@ class Web(object): """ def index_logic(): + self.add_request(self.REQUEST_LOAD, request.path) + r = make_response(render_template( 'receive.html', slug=self.slug, diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index cca2254d..656f912c 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -303,7 +303,8 @@ class Mode(QtWidgets.QWidget): """ Handle REQUEST_RATE_LIMIT event. """ - pass + self.stop_server() + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) def handle_request_progress(self, event): """ diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 5b65a052..bf661d86 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -93,6 +93,12 @@ class ReceiveMode(Mode): self.starting_server_step3.emit() self.start_server_finished.emit() + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) + def handle_request_close_server(self, event): """ Handle REQUEST_CLOSE_SERVER event. diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 15ba2f18..ea02340e 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -244,7 +244,7 @@ class ShareMode(Mode): """ Handle REQUEST_LOAD event. """ - self.status_bar.showMessage(strings._('download_page_loaded', True)) + self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) def handle_request_download(self, event): """ @@ -258,13 +258,6 @@ class ShareMode(Mode): self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) - def handle_request_rate_limit(self, event): - """ - Handle REQUEST_RATE_LIMIT event. - """ - self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) - def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. diff --git a/share/locale/en.json b/share/locale/en.json index 08678747..b6bd4f88 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -14,7 +14,6 @@ "not_a_readable_file": "{0:s} is not a readable file.", "no_filenames": "You must specify a list of files to share.", "no_available_port": "Could not start the Onion service as there was no available port.", - "download_page_loaded": "Download page loaded", "other_page_loaded": "Address loaded", "close_on_timeout": "Stopped because timer expired", "closing_automatically": "Stopped because download finished", @@ -181,5 +180,8 @@ "gui_settings_receive_allow_receiver_shutdown_checkbox": "Allow people who upload files to you to stop Receive Mode for you", "gui_settings_receive_public_mode_checkbox": "Receive Mode is open to the public\n(don't try to prevent people from guessing the OnionShare address)", "systray_close_server_title": "OnionShare Server Closed", - "systray_close_server_message": "The user closed the server" + "systray_close_server_message": "A user closed the server", + "systray_page_loaded_title": "OnionShare Page Loaded", + "systray_download_page_loaded_message": "A user loaded the download page", + "systray_upload_page_loaded_message": "A user loaded the upload page" } -- cgit v1.2.3-54-g00ecf From b6a15cf6c77c384686210a3cfecb677b9c17bf84 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:44:45 -0700 Subject: Display the URL without the slug for receive_public_mode in the CLI --- onionshare/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 05c50884..e2374c5f 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -162,6 +162,12 @@ def main(cwd=None): common.settings.set('slug', web.slug) common.settings.save() + # Build the URL + if receive and common.settings.get('receive_public_mode'): + url = 'http://{0:s}'.format(app.onion_host) + else: + url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) + print('') if receive: print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir'))) @@ -171,19 +177,19 @@ def main(cwd=None): if stealth: print(strings._("give_this_url_receive_stealth")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(url) print(app.auth_string) else: print(strings._("give_this_url_receive")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(url) else: if stealth: print(strings._("give_this_url_stealth")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(url) print(app.auth_string) else: print(strings._("give_this_url")) - print('http://{0:s}/{1:s}'.format(app.onion_host, web.slug)) + print(url) print('') print(strings._("ctrlc_to_stop")) -- cgit v1.2.3-54-g00ecf From 73efcf81fc681a7fe0cbe04ddd7a566ec243c759 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:47:36 -0700 Subject: Fix test from adding new settings --- test/test_onionshare_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 35e37f00..3942ab8c 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -64,7 +64,9 @@ class TestSettings: 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/OnionShare') + 'downloads_dir': os.path.expanduser('~/OnionShare'), + 'receive_allow_receiver_shutdown': True, + 'receive_public_mode': False } def test_fill_in_defaults(self, settings_obj): -- cgit v1.2.3-54-g00ecf From 70385dd6acef35227e3329c582e0e5bd92936cff Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 17:51:58 -0700 Subject: Write a simple Web test for share mode --- test/test_onionshare_web.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 75473596..cfcf8719 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -26,14 +26,65 @@ import re import socket import sys import zipfile +import tempfile import pytest from onionshare.common import Common +from onionshare.web import Web DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') + +def web_obj(common_obj, recieve_mode, num_files=0): + """ Creates a Web object, in either share mode or receive mode, ready for testing """ + web = Web(common_obj, False, recieve_mode) + web.generate_slug() + web.stay_open = True + web.app.testing = True + + # Share mode + if not recieve_mode: + # Add files + files = [] + for i in range(num_files): + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + files.append(tmp_file.name) + web.set_file_info(files) + # Receive mode + else: + pass + + return web + + +class TestWeb: + def test_share_mode(self, common_obj): + web = web_obj(common_obj, False, 3) + assert web.receive_mode is False + with web.app.test_client() as c: + # Load 404 pages + res = c.get('/') + assert res.status_code == 404 + + res = c.get('/invalidslug'.format(web.slug)) + assert res.status_code == 404 + + # Load download page + res = c.get('/{}'.format(web.slug)) + assert res.status_code == 200 + + # Download + res = c.get('/{}/download'.format(web.slug)) + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + + def test_share_mode_close_after_first_download(self, common_obj, temp_file_1024): + pass + + class TestZipWriterDefault: @pytest.mark.parametrize('test_input', ( 'onionshare_{}.zip'.format(''.join( -- cgit v1.2.3-54-g00ecf From 7ec993d2e0c9ddfcc5b38da32926b9711d66057f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 18:00:10 -0700 Subject: Implemented test: test_share_mode_close_after_first_download --- onionshare/web.py | 10 +++++++--- test/test_onionshare_web.py | 20 +++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 8d80810b..006150e9 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -246,9 +246,13 @@ class Web(object): # Close the server, if necessary if not self.stay_open and not canceled: print(strings._("closing_automatically")) - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() + self.running = False + try: + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() + except: + pass r = Response(generate()) r.headers.set('Content-Length', self.zip_filesize) diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index cfcf8719..f96b0c60 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -42,6 +42,8 @@ def web_obj(common_obj, recieve_mode, num_files=0): web = Web(common_obj, False, recieve_mode) web.generate_slug() web.stay_open = True + web.running = True + web.app.testing = True # Share mode @@ -67,22 +69,38 @@ class TestWeb: with web.app.test_client() as c: # Load 404 pages res = c.get('/') + res.get_data() assert res.status_code == 404 res = c.get('/invalidslug'.format(web.slug)) + res.get_data() assert res.status_code == 404 # Load download page res = c.get('/{}'.format(web.slug)) + res.get_data() assert res.status_code == 200 # Download res = c.get('/{}/download'.format(web.slug)) + res.get_data() assert res.status_code == 200 assert res.mimetype == 'application/zip' def test_share_mode_close_after_first_download(self, common_obj, temp_file_1024): - pass + web = web_obj(common_obj, False, 3) + web.stay_open = False + + assert web.running == True + + with web.app.test_client() as c: + # Download the first time + res = c.get('/{}/download'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + + assert web.running == False class TestZipWriterDefault: -- cgit v1.2.3-54-g00ecf From 86f1fb223ea8346b1e9150588f238e9945e38277 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 18:19:00 -0700 Subject: Add a few receive mode web tests, to test the receive_allow_receiver_shutdown and receive_public_mode settings --- onionshare/web.py | 11 +++-- test/conftest.py | 6 +++ test/test_onionshare_web.py | 97 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 006150e9..39bb6f34 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -495,10 +495,13 @@ class Web(object): Stop the flask web server, from the context of the flask app. """ # Shutdown the flask service - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('Not running with the Werkzeug Server') - func() + try: + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + except: + pass self.running = False def start(self, port, stay_open=False, persistent_slug=None): diff --git a/test/conftest.py b/test/conftest.py index e843bbbc..66dc02fb 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -152,3 +152,9 @@ def time_strftime(monkeypatch): @pytest.fixture def common_obj(): return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index f96b0c60..0b96359b 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -32,6 +32,7 @@ import pytest from onionshare.common import Common from onionshare.web import Web +from onionshare.settings import Settings DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') @@ -39,6 +40,8 @@ RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') def web_obj(common_obj, recieve_mode, num_files=0): """ Creates a Web object, in either share mode or receive mode, ready for testing """ + common_obj.load_settings() + web = Web(common_obj, False, recieve_mode) web.generate_slug() web.stay_open = True @@ -87,7 +90,7 @@ class TestWeb: assert res.status_code == 200 assert res.mimetype == 'application/zip' - def test_share_mode_close_after_first_download(self, common_obj, temp_file_1024): + def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024): web = web_obj(common_obj, False, 3) web.stay_open = False @@ -101,6 +104,98 @@ class TestWeb: assert res.mimetype == 'application/zip' assert web.running == False + + def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024): + web = web_obj(common_obj, False, 3) + web.stay_open = True + + assert web.running == True + + with web.app.test_client() as c: + # Download the first time + res = c.get('/{}/download'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + assert web.running == True + + def test_receive_mode(self, common_obj): + web = web_obj(common_obj, True) + assert web.receive_mode is True + + with web.app.test_client() as c: + # Load 404 pages + res = c.get('/') + res.get_data() + assert res.status_code == 404 + + res = c.get('/invalidslug'.format(web.slug)) + res.get_data() + assert res.status_code == 404 + + # Load upload page + res = c.get('/{}'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + + def test_receive_mode_allow_receiver_shutdown_on(self, common_obj): + web = web_obj(common_obj, True) + + common_obj.settings.set('receive_allow_receiver_shutdown', True) + + assert web.running == True + + with web.app.test_client() as c: + # Load close page + res = c.post('/{}/close'.format(web.slug)) + res.get_data() + # Should return ok, and server should stop + assert res.status_code == 200 + assert web.running == False + + def test_receive_mode_allow_receiver_shutdown_off(self, common_obj): + web = web_obj(common_obj, True) + + common_obj.settings.set('receive_allow_receiver_shutdown', False) + + assert web.running == True + + with web.app.test_client() as c: + # Load close page + res = c.post('/{}/close'.format(web.slug)) + res.get_data() + # Should redirect to index, and server should still be running + assert res.status_code == 302 + assert web.running == True + + def test_receive_mode_receive_public_mode_on(self, common_obj): + web = web_obj(common_obj, True) + common_obj.settings.set('receive_public_mode', True) + + with web.app.test_client() as c: + # Upload page should be accessible from both / and /[slug] + res = c.get('/') + data1 = res.get_data() + assert res.status_code == 200 + + res = c.get('/{}'.format(web.slug)) + data2 = res.get_data() + assert res.status_code == 200 + + def test_receive_mode_receive_public_mode_off(self, common_obj): + web = web_obj(common_obj, True) + common_obj.settings.set('receive_public_mode', False) + + with web.app.test_client() as c: + # / should be a 404 + res = c.get('/') + data1 = res.get_data() + assert res.status_code == 404 + + # Upload page should be accessible from both /[slug] + res = c.get('/{}'.format(web.slug)) + data2 = res.get_data() + assert res.status_code == 200 class TestZipWriterDefault: -- cgit v1.2.3-54-g00ecf From c0904b1b8db758c0fe7115c847d0242955fc1b79 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 30 Apr 2018 10:01:23 -0700 Subject: Fix issue in test that flake discovered --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 66dc02fb..610a43ea 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -8,7 +8,7 @@ import tempfile import pytest -from onionshare import common, web +from onionshare import common, web, settings @pytest.fixture def temp_dir_1024(): -- cgit v1.2.3-54-g00ecf From 97d42492e5276357a846a1453ac2476901ffacc7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 2 May 2018 14:41:15 +1000 Subject: Fix python setup process for share/static dir contents --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 99222ef0..460eb476 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,9 @@ data_files=[ (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')), (os.path.join(sys.prefix, 'share/onionshare/templates'), file_list('share/templates')), - (os.path.join(sys.prefix, 'share/onionshare/static'), file_list('share/static')) + (os.path.join(sys.prefix, 'share/onionshare/static/css'), file_list('share/static/css')), + (os.path.join(sys.prefix, 'share/onionshare/static/img'), file_list('share/static/img')), + (os.path.join(sys.prefix, 'share/onionshare/static/js'), file_list('share/static/js')) ] if platform.system() != 'OpenBSD': data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])) -- cgit v1.2.3-54-g00ecf From 65dff32702a33ae6f968f907003dacaf307db4e5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 3 May 2018 09:14:16 -0700 Subject: Make clicking the mode switcher buttons properly adjust the size of the window --- onionshare_gui/onionshare_gui.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index f0820ac4..8db05257 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -55,7 +55,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.setMinimumWidth(430) + self.setMinimumWidth(450) # Load settings self.config = config @@ -236,21 +236,24 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.show() self.update_server_status_indicator() - self.adjustSize(); + + # Wait 1ms for the event loop to finish, then adjust size + QtCore.QTimer.singleShot(1, self.adjustSize) def share_mode_clicked(self): - self.mode = self.MODE_SHARE - self.update_mode_switcher() + if self.mode != self.MODE_SHARE: + self.common.log('OnionShareGui', 'share_mode_clicked') + self.mode = self.MODE_SHARE + self.update_mode_switcher() def receive_mode_clicked(self): - self.mode = self.MODE_RECEIVE - self.update_mode_switcher() + if self.mode != self.MODE_RECEIVE: + self.common.log('OnionShareGui', 'receive_mode_clicked') + self.mode = self.MODE_RECEIVE + self.update_mode_switcher() def update_server_status_indicator(self): - self.common.log('OnionShareGui', 'update_server_status_indicator') - # Set the status image - if self.mode == self.MODE_SHARE: # Share mode if self.share_mode.server_status.status == ServerStatus.STATUS_STOPPED: -- cgit v1.2.3-54-g00ecf From 07152ad96929b963e0ade82a86e21774e5fed8da Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 3 May 2018 09:29:54 -0700 Subject: Start creating the information widget for receive mode, and refactor for share mode --- onionshare_gui/receive_mode/__init__.py | 56 +++++++++++++++++++++++++++++++++ onionshare_gui/share_mode/__init__.py | 50 +++++++++++++++-------------- 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index bf661d86..5b570fab 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -44,6 +44,38 @@ class ReceiveMode(Mode): self.server_status.web = self.web self.server_status.update() + # Downloads + #self.uploads = Uploads(self.common) + self.uploads_in_progress = 0 + self.uploads_completed = 0 + self.new_upload = False # For scrolling to the bottom of the uploads list + + # Information about share, and show uploads button + self.info_show_uploads = QtWidgets.QToolButton() + self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.info_show_uploads.setCheckable(True) + #self.info_show_uploads.toggled.connect(self.downloads_toggled) + self.info_show_uploads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + + self.info_in_progress_uploads_count = QtWidgets.QLabel() + self.info_in_progress_uploads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.info_completed_uploads_count = QtWidgets.QLabel() + self.info_completed_uploads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + + self.update_uploads_completed() + self.update_uploads_in_progress() + + self.info_layout = QtWidgets.QHBoxLayout() + self.info_layout.addStretch() + self.info_layout.addWidget(self.info_in_progress_uploads_count) + self.info_layout.addWidget(self.info_completed_uploads_count) + self.info_layout.addWidget(self.info_show_uploads) + + self.info_widget = QtWidgets.QWidget() + self.info_widget.setLayout(self.info_layout) + self.info_widget.hide() + # Receive mode info self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) self.receive_info.setMinimumHeight(80) @@ -51,6 +83,7 @@ class ReceiveMode(Mode): # Layout self.layout.insertWidget(0, self.receive_info) + self.layout.insertWidget(0, self.info_widget) def timer_callback_custom(self): """ @@ -105,3 +138,26 @@ class ReceiveMode(Mode): """ self.stop_server() self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) + + def update_uploads_completed(self): + """ + Update the 'Downloads completed' info widget. + """ + if self.uploads_completed == 0: + image = self.common.get_resource_path('images/download_completed_none.png') + else: + image = self.common.get_resource_path('images/download_completed.png') + self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) + self.info_completed_uploads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.uploads_completed)) + + def update_uploads_in_progress(self): + """ + Update the 'Downloads in progress' info widget. + """ + if self.uploads_in_progress == 0: + image = self.common.get_resource_path('images/download_in_progress_none.png') + else: + image = self.common.get_resource_path('images/download_in_progress.png') + self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) + self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) + self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress)) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index ea02340e..c6e02bf2 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -73,8 +73,7 @@ class ShareMode(Mode): self.downloads_completed = 0 self.new_download = False # For scrolling to the bottom of the downloads list - # Info label along top of screen - self.info_layout = QtWidgets.QHBoxLayout() + # Information about share, and show downloads button self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') @@ -90,9 +89,10 @@ class ShareMode(Mode): self.info_completed_downloads_count = QtWidgets.QLabel() self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') - self.update_downloads_completed(self.downloads_in_progress) - self.update_downloads_in_progress(self.downloads_in_progress) + self.update_downloads_completed() + self.update_downloads_in_progress() + self.info_layout = QtWidgets.QHBoxLayout() self.info_layout.addWidget(self.info_label) self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) @@ -230,7 +230,7 @@ class ShareMode(Mode): self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 - self.update_downloads_in_progress(0) + self.update_downloads_in_progress() self.file_selection.file_list.adjustSize() def handle_tor_broke_custom(self): @@ -254,7 +254,7 @@ class ShareMode(Mode): self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 - self.update_downloads_in_progress(self.downloads_in_progress) + self.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) @@ -270,10 +270,10 @@ class ShareMode(Mode): # Update the total 'completed downloads' info self.downloads_completed += 1 - self.update_downloads_completed(self.downloads_completed) + self.update_downloads_completed() # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) + self.update_downloads_in_progress() # close on finish? if not self.web.stay_open: @@ -284,7 +284,7 @@ class ShareMode(Mode): if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel_download(event["data"]["id"]) self.downloads_in_progress = 0 - self.update_downloads_in_progress(self.downloads_in_progress) + self.update_downloads_in_progress() def handle_request_canceled(self, event): """ @@ -294,7 +294,7 @@ class ShareMode(Mode): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.update_downloads_in_progress(self.downloads_in_progress) + self.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) def on_reload_settings(self): @@ -346,34 +346,36 @@ class ShareMode(Mode): """ Set the info counters back to zero. """ - self.update_downloads_completed(0) - self.update_downloads_in_progress(0) + self.downloads_completed = 0 + self.downloads_in_progress = 0 + self.update_downloads_completed() + self.update_downloads_in_progress() self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) self.downloads.no_downloads_label.show() self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) - def update_downloads_completed(self, count): + def update_downloads_completed(self): """ Update the 'Downloads completed' info widget. """ - if count == 0: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png') + if self.downloads_completed == 0: + image = self.common.get_resource_path('images/download_completed_none.png') else: - self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png') - self.info_completed_downloads_count.setText(' {1:d}'.format(self.info_completed_downloads_image, count)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count)) + image = self.common.get_resource_path('images/download_completed.png') + self.info_completed_downloads_count.setText(' {1:d}'.format(image, self.downloads_completed)) + self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed)) - def update_downloads_in_progress(self, count): + def update_downloads_in_progress(self): """ Update the 'Downloads in progress' info widget. """ - if count == 0: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png') + if self.downloads_in_progress == 0: + image = self.common.get_resource_path('images/download_in_progress_none.png') else: - self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png') + image = self.common.get_resource_path('images/download_in_progress.png') self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) - self.info_in_progress_downloads_count.setText(' {1:d}'.format(self.info_in_progress_downloads_image, count)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count)) + self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) @staticmethod def _compute_total_size(filenames): -- cgit v1.2.3-54-g00ecf From 3cdd546bd3aa2b274c65f61445d70fa578c8470d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 15:40:22 -0700 Subject: Ignore __pycache__ folders --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b202993b..12201adb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +__pycache__ *.py[cod] # C extensions -- cgit v1.2.3-54-g00ecf From ed28fdf123867dfda2b6aa4016e436d8e9bd1069 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 15:53:34 -0700 Subject: Make receive mode info widget show when server is stated, hide when it is not started --- onionshare_gui/mode.py | 24 +++++++++++----------- onionshare_gui/receive_mode/__init__.py | 35 ++++++++++++++++++++++----------- onionshare_gui/server_status.py | 6 ++++-- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 656f912c..b6910375 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -51,6 +51,8 @@ class Mode(QtWidgets.QWidget): self.filenames = filenames + self.setMinimumWidth(450) + # The web object gets created in init() self.web = None @@ -107,26 +109,26 @@ class Mode(QtWidgets.QWidget): Add custom timer code. """ pass - + def get_stop_server_shutdown_timeout_text(self): """ Return the string to put on the stop server button, if there's a shutdown timeout """ pass - + def timeout_finished_should_stop_server(self): """ The shutdown timer expired, should we stop the server? Returns a bool """ pass - + def start_server(self): """ Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ self.common.log('Mode', 'start_server') - + self.start_server_custom() self.set_server_active.emit(True) @@ -165,7 +167,7 @@ class Mode(QtWidgets.QWidget): self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True self.t.start() - + def start_server_custom(self): """ Add custom initialization here. @@ -185,7 +187,7 @@ class Mode(QtWidgets.QWidget): # start_server_step2_custom has call these to move on: # self.starting_server_step3.emit() # self.start_server_finished.emit() - + def start_server_step2_custom(self): """ Add custom initialization here. @@ -194,7 +196,7 @@ class Mode(QtWidgets.QWidget): def start_server_step3(self): """ - Step 3 in starting the onionshare server. + Step 3 in starting the onionshare server. """ self.common.log('Mode', 'start_server_step3') @@ -231,7 +233,7 @@ class Mode(QtWidgets.QWidget): self.status_bar.clearMessage() self.start_server_error_custom() - + def start_server_error_custom(self): """ Add custom initialization here. @@ -270,7 +272,7 @@ class Mode(QtWidgets.QWidget): Add custom initialization here. """ pass - + def handle_tor_broke(self): """ Handle connection from Tor breaking. @@ -278,7 +280,7 @@ class Mode(QtWidgets.QWidget): if self.server_status.status != ServerStatus.STATUS_STOPPED: self.server_status.stop_server() self.handle_tor_broke_custom() - + def handle_tor_broke_custom(self): """ Add custom initialization here. @@ -317,7 +319,7 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_CANCELED event. """ pass - + def handle_request_close_server(self, event): """ Handle REQUEST_CLOSE_SERVER event. diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 5b570fab..27147db0 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -37,9 +37,10 @@ class ReceiveMode(Mode): # Server status self.server_status.set_mode('receive') - #self.server_status.server_stopped.connect(self.update_primary_action) - #self.server_status.server_canceled.connect(self.update_primary_action) - + self.server_status.server_started_finished.connect(self.update_primary_action) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.update_primary_action) + # Tell server_status about web, then update self.server_status.web = self.web self.server_status.update() @@ -84,7 +85,7 @@ class ReceiveMode(Mode): # Layout self.layout.insertWidget(0, self.receive_info) self.layout.insertWidget(0, self.info_widget) - + def timer_callback_custom(self): """ This method is called regularly on a timer while share mode is active. @@ -93,31 +94,31 @@ class ReceiveMode(Mode): #if self.new_download: # self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) # self.new_download = False - + def get_stop_server_shutdown_timeout_text(self): """ 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) - + def timeout_finished_should_stop_server(self): """ The shutdown timer expired, should we stop the server? Returns a bool """ # TODO: wait until the final upload is done before stoppign the server? return True - + def start_server_custom(self): """ Starting the server. """ # Reset web counters self.web.error404_count = 0 - + # Hide and reset the downloads if we have previously shared #self.downloads.reset_downloads() #self.reset_info_counters() - + def start_server_step2_custom(self): """ Step 2 in starting the server. @@ -125,13 +126,13 @@ class ReceiveMode(Mode): # Continue self.starting_server_step3.emit() self.start_server_finished.emit() - + def handle_request_load(self, event): """ Handle REQUEST_LOAD event. """ self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) - + def handle_request_close_server(self, event): """ Handle REQUEST_CLOSE_SERVER event. @@ -161,3 +162,15 @@ class ReceiveMode(Mode): self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress)) + + def update_primary_action(self): + self.common.log('ReceiveMode', 'update_primary_action') + + # Show the info widget when the server is active + if self.server_status.status == self.server_status.STATUS_STARTED: + self.info_widget.show() + else: + self.info_widget.hide() + + # Resize window + self.adjustSize() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index c0a7172f..ee60f432 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -29,6 +29,7 @@ class ServerStatus(QtWidgets.QWidget): The server status chunk of the GUI. """ server_started = QtCore.pyqtSignal() + server_started_finished = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal() server_canceled = QtCore.pyqtSignal() button_clicked = QtCore.pyqtSignal() @@ -116,7 +117,7 @@ class ServerStatus(QtWidgets.QWidget): layout.addLayout(url_layout) layout.addWidget(self.shutdown_timeout_container) self.setLayout(layout) - + def set_mode(self, share_mode, file_selection=None): """ The server status is in share mode. @@ -268,6 +269,7 @@ class ServerStatus(QtWidgets.QWidget): self.status = self.STATUS_STARTED self.copy_url() self.update() + self.server_started_finished.emit() def stop_server(self): """ @@ -312,7 +314,7 @@ class ServerStatus(QtWidgets.QWidget): clipboard.setText(self.app.auth_string) self.hidservauth_copied.emit() - + def get_url(self): """ Returns the OnionShare URL. -- cgit v1.2.3-54-g00ecf From 30c9f50d2ee611267970b758c1a112fa3292b409 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:06:24 -0700 Subject: Refactor ReceiveMode and Downloads, to push more download-related logic into Downloads --- onionshare_gui/share_mode/__init__.py | 34 ++++++++++------------------------ onionshare_gui/share_mode/downloads.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index c6e02bf2..9c98554d 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -47,7 +47,7 @@ class ShareMode(Mode): if self.filenames: for filename in self.filenames: self.file_selection.file_list.add_file(filename) - + # Server status self.server_status.set_mode('share', self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) @@ -71,7 +71,6 @@ class ShareMode(Mode): self.downloads = Downloads(self.common) self.downloads_in_progress = 0 self.downloads_completed = 0 - self.new_download = False # For scrolling to the bottom of the downloads list # Information about share, and show downloads button self.info_label = QtWidgets.QLabel() @@ -118,21 +117,12 @@ class ShareMode(Mode): # Always start with focus on file selection self.file_selection.setFocus() - def timer_callback_custom(self): - """ - This method is called regularly on a timer while share mode is active. - """ - # Scroll to the bottom of the download progress bar log pane if a new download has been added - if self.new_download: - self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) - self.new_download = False - def get_stop_server_shutdown_timeout_text(self): """ 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) - + def timeout_finished_should_stop_server(self): """ The shutdown timer expired, should we stop the server? Returns a bool @@ -154,11 +144,10 @@ class ShareMode(Mode): # Reset web counters self.web.download_count = 0 self.web.error404_count = 0 - + # Hide and reset the downloads if we have previously shared - self.downloads.reset_downloads() self.reset_info_counters() - + def start_server_step2_custom(self): """ Step 2 in starting the server. Zipping up files. @@ -178,7 +167,7 @@ class ShareMode(Mode): def _set_processed_size(x): if self._zip_progress_bar != None: self._zip_progress_bar.update_processed_size_signal.emit(x) - + try: self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) self.app.cleanup_filenames.append(self.web.zip_filename) @@ -194,7 +183,7 @@ class ShareMode(Mode): t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t.daemon = True t.start() - + def start_server_step3_custom(self): """ Step 3 in starting the server. Remove zip progess bar, and display large filesize @@ -209,7 +198,7 @@ class ShareMode(Mode): if self.web.zip_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() - + def start_server_error_custom(self): """ Start server error. @@ -217,7 +206,7 @@ class ShareMode(Mode): if self._zip_progress_bar is not None: self.status_bar.removeWidget(self._zip_progress_bar) self._zip_progress_bar = None - + def stop_server_custom(self): """ Stop server. @@ -250,9 +239,7 @@ class ShareMode(Mode): """ Handle REQUEST_DOWNLOAD event. """ - self.downloads.no_downloads_label.hide() self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) - self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress() @@ -351,8 +338,7 @@ class ShareMode(Mode): self.update_downloads_completed() self.update_downloads_in_progress() self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.downloads.no_downloads_label.show() - self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint()) + self.downloads.reset_downloads() def update_downloads_completed(self): """ @@ -376,7 +362,7 @@ class ShareMode(Mode): self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) - + @staticmethod def _compute_total_size(filenames): total_size = 0 diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 31298370..cc624353 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -131,11 +131,17 @@ class Downloads(QtWidgets.QWidget): """ Add a new download progress bar. """ - # add it to the list + # Hide the no_downloads_label + self.no_downloads_label.hide() + + # 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) + # Scroll to the bottom + self.downloads_container.vbar.setValue(self.downloads_container.vbar.maximum()) + def update_download(self, download_id, downloaded_bytes): """ Update the progress of a download progress bar. @@ -156,3 +162,6 @@ class Downloads(QtWidgets.QWidget): self.downloads_layout.removeWidget(download.progress_bar) download.progress_bar.close() self.downloads = {} + + self.no_downloads_label.show() + self.downloads_container.resize(self.downloads_container.sizeHint()) -- cgit v1.2.3-54-g00ecf From dcea4595807e5bb6c158620133f2b7fac54be64e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:26:54 -0700 Subject: Start building Uploads widget --- onionshare_gui/receive_mode/__init__.py | 3 ++- onionshare_gui/receive_mode/uploads.py | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 onionshare_gui/receive_mode/uploads.py diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 27147db0..c306d8a9 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -22,6 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.web import Web +from .uploads import Uploads from ..mode import Mode class ReceiveMode(Mode): @@ -46,7 +47,7 @@ class ReceiveMode(Mode): self.server_status.update() # Downloads - #self.uploads = Uploads(self.common) + self.uploads = Uploads(self.common) self.uploads_in_progress = 0 self.uploads_completed = 0 self.new_upload = False # For scrolling to the bottom of the uploads list diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py new file mode 100644 index 00000000..821e4440 --- /dev/null +++ b/onionshare_gui/receive_mode/uploads.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +class Uploads(QtWidgets.QWidget): + """ + 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.layout = QtWidgets.QVBoxLayout() + self.layout.addStretch() + self.setLayout(self.layout) -- cgit v1.2.3-54-g00ecf From e32e850548b97152d335882917b192b90ea96641 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:35:32 -0700 Subject: Fix stay_open regression bug. Before, it was closing automatically even when the setting wasn't set. Also, remove the --stay-open option from the GUI, since GUI settings are set in the settings dialog not cli args --- onionshare/__init__.py | 5 ++--- onionshare/onionshare.py | 7 ++----- onionshare_gui/__init__.py | 4 +--- onionshare_gui/mode.py | 4 +--- onionshare_gui/share_mode/__init__.py | 4 ++-- test/test_onionshare.py | 1 - 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index e2374c5f..86f03b84 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -106,7 +106,6 @@ def main(cwd=None): # Create the Web object web = Web(common, False, receive) - web.stay_open = stay_open # Start the Onion object onion = Onion(common) @@ -120,7 +119,7 @@ def main(cwd=None): # Start the onionshare app try: - app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, shutdown_timeout) app.set_stealth(stealth) app.choose_port() app.start_onion_service() @@ -144,7 +143,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, app.stay_open, common.settings.get('slug'))) + t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('slug'))) t.daemon = True t.start() diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index e7306510..09d32cb2 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -28,7 +28,7 @@ class OnionShare(object): OnionShare is the main application class. Pass in options and run start_onion_service and it will do the magic. """ - def __init__(self, common, onion, local_only=False, stay_open=False, shutdown_timeout=0): + def __init__(self, common, onion, local_only=False, shutdown_timeout=0): self.common = common self.common.log('OnionShare', '__init__') @@ -47,9 +47,6 @@ class OnionShare(object): # do not use tor -- for development self.local_only = local_only - # automatically close when download is finished - self.stay_open = stay_open - # optionally shut down after N hours self.shutdown_timeout = shutdown_timeout # init timing thread @@ -60,7 +57,7 @@ class OnionShare(object): self.stealth = stealth self.onion.stealth = stealth - + def choose_port(self): """ Choose a random port. diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index a46fe009..c3df057b 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -64,7 +64,6 @@ def main(): # Parse arguments parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) - parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open")) parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename')) @@ -79,7 +78,6 @@ def main(): config = args.config local_only = bool(args.local_only) - stay_open = bool(args.stay_open) shutdown_timeout = int(args.shutdown_timeout) debug = bool(args.debug) @@ -103,7 +101,7 @@ def main(): onion = Onion(common) # Start the OnionShare app - app = OnionShare(common, onion, local_only, stay_open, shutdown_timeout) + app = OnionShare(common, onion, local_only, shutdown_timeout) # Launch the gui gui = OnionShareGui(common, onion, qtapp, app, filenames, config, local_only) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index b6910375..d425341e 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -144,7 +144,7 @@ class Mode(QtWidgets.QWidget): self.app.choose_port() # Start http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug'))) + t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('slug'))) t.daemon = True t.start() @@ -161,8 +161,6 @@ class Mode(QtWidgets.QWidget): self.starting_server_error.emit(e.args[0]) return - self.app.stay_open = not self.common.settings.get('close_after_first_download') - self.common.log('Mode', 'start_server', 'Starting an onion thread') self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) self.t.daemon = True diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 9c98554d..91046ad9 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -262,8 +262,8 @@ class ShareMode(Mode): self.downloads_in_progress -= 1 self.update_downloads_in_progress() - # close on finish? - if not self.web.stay_open: + # Close on finish? + if self.common.settings.get('close_after_first_download'): self.server_status.stop_server() self.status_bar.clearMessage() self.server_status_label.setText(strings._('closing_automatically', True)) diff --git a/test/test_onionshare.py b/test/test_onionshare.py index 19b488df..7592a777 100644 --- a/test/test_onionshare.py +++ b/test/test_onionshare.py @@ -49,7 +49,6 @@ class TestOnionShare: assert onionshare_obj.stealth is None assert onionshare_obj.cleanup_filenames == [] assert onionshare_obj.local_only is False - assert onionshare_obj.stay_open is False def test_set_stealth_true(self, onionshare_obj): onionshare_obj.set_stealth(True) -- cgit v1.2.3-54-g00ecf From 7b25ae1d6b66786aa6cecc5bc99d3bdcae8ac408 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:43:30 -0700 Subject: Remove --shutdown-timeout as an option for onionshare_gui, since GUI options are set in the settings dialog. Also fixed a bug where --local-only and --shutdown-timeout were not compatible in onionshare CLI --- onionshare/onionshare.py | 6 +++--- onionshare_gui/__init__.py | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 09d32cb2..b710fa3c 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -76,13 +76,13 @@ class OnionShare(object): if not self.port: self.choose_port() + if self.shutdown_timeout > 0: + self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout) + if self.local_only: self.onion_host = '127.0.0.1:{0:d}'.format(self.port) return - if self.shutdown_timeout > 0: - self.shutdown_timer = ShutdownTimer(self.common, self.shutdown_timeout) - self.onion_host = self.onion.start_onion_service(self.port) if self.stealth: diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index c3df057b..6254676c 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -64,7 +64,6 @@ def main(): # Parse arguments parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=48)) parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only")) - parser.add_argument('--shutdown-timeout', metavar='', dest='shutdown_timeout', default=0, help=strings._("help_shutdown_timeout")) parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug")) parser.add_argument('--filenames', metavar='filenames', nargs='+', help=strings._('help_filename')) parser.add_argument('--config', metavar='config', default=False, help=strings._('help_config')) @@ -78,7 +77,6 @@ def main(): config = args.config local_only = bool(args.local_only) - shutdown_timeout = int(args.shutdown_timeout) debug = bool(args.debug) # Debug mode? @@ -101,7 +99,7 @@ def main(): onion = Onion(common) # Start the OnionShare app - app = OnionShare(common, onion, local_only, shutdown_timeout) + app = OnionShare(common, onion, local_only) # Launch the gui gui = OnionShareGui(common, onion, qtapp, app, filenames, config, local_only) -- cgit v1.2.3-54-g00ecf From 3f624a4a27c4a41ab20f8111359de4896e08a7c2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:57:17 -0700 Subject: Refactor ShareMode and Downloads to remove the Downloads container widget, and make Downloads itself the QScrollArea --- onionshare_gui/share_mode/__init__.py | 4 +-- onionshare_gui/share_mode/downloads.py | 45 +++++++++++++++++----------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 91046ad9..55d21317 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -325,9 +325,9 @@ class ShareMode(Mode): """ self.common.log('ShareMode', 'toggle_downloads') if checked: - self.downloads.downloads_container.show() + self.downloads.show() else: - self.downloads.downloads_container.hide() + self.downloads.hide() def reset_info_counters(self): """ diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index cc624353..5e267bbe 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -91,41 +91,40 @@ class Download(object): self.started) -class Downloads(QtWidgets.QWidget): +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.downloads_container = QtWidgets.QScrollArea() - self.downloads_container.setWidget(self) - self.downloads_container.setWindowTitle(strings._('gui_downloads', True)) - self.downloads_container.setWidgetResizable(True) - self.downloads_container.setMaximumHeight(600) - self.downloads_container.setMinimumHeight(150) - self.downloads_container.setMinimumWidth(350) - self.downloads_container.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) - self.downloads_container.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) - self.downloads_container.vbar = self.downloads_container.verticalScrollBar() - - self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + self.setWindowTitle(strings._('gui_downloads', True)) + self.setWidgetResizable(True) + self.setMaximumHeight(600) + 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() + + downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) self.downloads_layout = QtWidgets.QVBoxLayout() - self.layout = QtWidgets.QVBoxLayout() - self.layout.addWidget(self.downloads_label) - self.layout.addWidget(self.no_downloads_label) - self.layout.addLayout(self.downloads_layout) - self.layout.addStretch() - self.setLayout(self.layout) + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(downloads_label) + layout.addWidget(self.no_downloads_label) + layout.addLayout(self.downloads_layout) + layout.addStretch() + widget.setLayout(layout) + self.setWidget(widget) def add_download(self, download_id, total_bytes): """ @@ -140,7 +139,7 @@ class Downloads(QtWidgets.QWidget): self.downloads_layout.addWidget(download.progress_bar) # Scroll to the bottom - self.downloads_container.vbar.setValue(self.downloads_container.vbar.maximum()) + self.vbar.setValue(self.vbar.maximum()) def update_download(self, download_id, downloaded_bytes): """ @@ -164,4 +163,4 @@ class Downloads(QtWidgets.QWidget): self.downloads = {} self.no_downloads_label.show() - self.downloads_container.resize(self.downloads_container.sizeHint()) + self.resize(self.sizeHint()) -- cgit v1.2.3-54-g00ecf From c77eba7f23bce6147023ac5580621d6c5ca45e5c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 17:33:18 -0700 Subject: Tweak language of receive mode options --- share/locale/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index b6bd4f88..50bb53c4 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -177,8 +177,8 @@ "gui_settings_receiving_label": "Receiving options", "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "Allow people who upload files to you to stop Receive Mode for you", - "gui_settings_receive_public_mode_checkbox": "Receive Mode is open to the public\n(don't try to prevent people from guessing the OnionShare address)", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", + "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", -- cgit v1.2.3-54-g00ecf From be36f3a4b6ab7a7b2920a5ced60a82d0bce51bda Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 17:57:30 -0700 Subject: Rename some images to reuse in ReceiveMode, and make new upload window button images --- onionshare_gui/receive_mode/__init__.py | 26 ++++++++++++++++-------- onionshare_gui/receive_mode/uploads.py | 31 ++++++++++++++++++++++++----- onionshare_gui/share_mode/__init__.py | 8 ++++---- share/images/download_completed.png | Bin 646 -> 0 bytes share/images/download_completed_none.png | Bin 437 -> 0 bytes share/images/download_in_progress.png | Bin 638 -> 0 bytes share/images/download_in_progress_none.png | Bin 412 -> 0 bytes share/images/share_completed.png | Bin 0 -> 646 bytes share/images/share_completed_none.png | Bin 0 -> 437 bytes share/images/share_in_progress.png | Bin 0 -> 638 bytes share/images/share_in_progress_none.png | Bin 0 -> 412 bytes share/images/upload_window_gray.png | Bin 0 -> 298 bytes share/images/upload_window_green.png | Bin 0 -> 483 bytes share/locale/en.json | 5 ++++- 14 files changed, 52 insertions(+), 18 deletions(-) delete mode 100644 share/images/download_completed.png delete mode 100644 share/images/download_completed_none.png delete mode 100644 share/images/download_in_progress.png delete mode 100644 share/images/download_in_progress_none.png create mode 100644 share/images/share_completed.png create mode 100644 share/images/share_completed_none.png create mode 100644 share/images/share_in_progress.png create mode 100644 share/images/share_in_progress_none.png create mode 100644 share/images/upload_window_gray.png create mode 100644 share/images/upload_window_green.png diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index c306d8a9..ac904600 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -54,10 +54,10 @@ class ReceiveMode(Mode): # Information about share, and show uploads button self.info_show_uploads = QtWidgets.QToolButton() - self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) + self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png'))) self.info_show_uploads.setCheckable(True) - #self.info_show_uploads.toggled.connect(self.downloads_toggled) - self.info_show_uploads.setToolTip(strings._('gui_downloads_window_tooltip', True)) + self.info_show_uploads.toggled.connect(self.uploads_toggled) + self.info_show_uploads.setToolTip(strings._('gui_uploads_window_tooltip', True)) self.info_in_progress_uploads_count = QtWidgets.QLabel() self.info_in_progress_uploads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') @@ -146,9 +146,9 @@ class ReceiveMode(Mode): Update the 'Downloads completed' info widget. """ if self.uploads_completed == 0: - image = self.common.get_resource_path('images/download_completed_none.png') + image = self.common.get_resource_path('images/share_completed_none.png') else: - image = self.common.get_resource_path('images/download_completed.png') + image = self.common.get_resource_path('images/share_completed.png') self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) self.info_completed_uploads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.uploads_completed)) @@ -157,10 +157,10 @@ class ReceiveMode(Mode): Update the 'Downloads in progress' info widget. """ if self.uploads_in_progress == 0: - image = self.common.get_resource_path('images/download_in_progress_none.png') + image = self.common.get_resource_path('images/share_in_progress_none.png') else: - image = self.common.get_resource_path('images/download_in_progress.png') - self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) + image = self.common.get_resource_path('images/share_in_progress.png') + self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png'))) self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress)) @@ -175,3 +175,13 @@ class ReceiveMode(Mode): # Resize window self.adjustSize() + + def uploads_toggled(self, checked): + """ + When the 'Show/hide uploads' button is toggled, show or hide the uploads window. + """ + self.common.log('ReceiveMode', 'toggle_uploads') + if checked: + self.uploads.show() + else: + self.uploads.hide() diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 821e4440..445e9406 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -21,16 +21,37 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -class Uploads(QtWidgets.QWidget): +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.layout = QtWidgets.QVBoxLayout() - self.layout.addStretch() - self.setLayout(self.layout) + self.uploads = {} + + self.setWindowTitle(strings._('gui_uploads', True)) + self.setWidgetResizable(True) + self.setMaximumHeight(600) + 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() + + uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) + uploads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True)) + + self.uploads_layout = QtWidgets.QVBoxLayout() + + widget = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(uploads_label) + layout.addWidget(self.no_uploads_label) + layout.addLayout(self.uploads_layout) + layout.addStretch() + widget.setLayout(layout) + self.setWidget(widget) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 55d21317..97d3d23f 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -345,9 +345,9 @@ class ShareMode(Mode): Update the 'Downloads completed' info widget. """ if self.downloads_completed == 0: - image = self.common.get_resource_path('images/download_completed_none.png') + image = self.common.get_resource_path('images/share_completed_none.png') else: - image = self.common.get_resource_path('images/download_completed.png') + image = self.common.get_resource_path('images/share_completed.png') self.info_completed_downloads_count.setText(' {1:d}'.format(image, self.downloads_completed)) self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed)) @@ -356,9 +356,9 @@ class ShareMode(Mode): Update the 'Downloads in progress' info widget. """ if self.downloads_in_progress == 0: - image = self.common.get_resource_path('images/download_in_progress_none.png') + image = self.common.get_resource_path('images/share_in_progress_none.png') else: - image = self.common.get_resource_path('images/download_in_progress.png') + image = self.common.get_resource_path('images/share_in_progress.png') self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) diff --git a/share/images/download_completed.png b/share/images/download_completed.png deleted file mode 100644 index e68fe5a2..00000000 Binary files a/share/images/download_completed.png and /dev/null differ diff --git a/share/images/download_completed_none.png b/share/images/download_completed_none.png deleted file mode 100644 index 8dbd6939..00000000 Binary files a/share/images/download_completed_none.png and /dev/null differ diff --git a/share/images/download_in_progress.png b/share/images/download_in_progress.png deleted file mode 100644 index 19694659..00000000 Binary files a/share/images/download_in_progress.png and /dev/null differ diff --git a/share/images/download_in_progress_none.png b/share/images/download_in_progress_none.png deleted file mode 100644 index 2d61dba4..00000000 Binary files a/share/images/download_in_progress_none.png and /dev/null differ diff --git a/share/images/share_completed.png b/share/images/share_completed.png new file mode 100644 index 00000000..e68fe5a2 Binary files /dev/null and b/share/images/share_completed.png differ diff --git a/share/images/share_completed_none.png b/share/images/share_completed_none.png new file mode 100644 index 00000000..8dbd6939 Binary files /dev/null and b/share/images/share_completed_none.png differ diff --git a/share/images/share_in_progress.png b/share/images/share_in_progress.png new file mode 100644 index 00000000..19694659 Binary files /dev/null and b/share/images/share_in_progress.png differ diff --git a/share/images/share_in_progress_none.png b/share/images/share_in_progress_none.png new file mode 100644 index 00000000..2d61dba4 Binary files /dev/null and b/share/images/share_in_progress_none.png differ diff --git a/share/images/upload_window_gray.png b/share/images/upload_window_gray.png new file mode 100644 index 00000000..80db4b8f Binary files /dev/null and b/share/images/upload_window_gray.png differ diff --git a/share/images/upload_window_green.png b/share/images/upload_window_green.png new file mode 100644 index 00000000..652ddaff Binary files /dev/null and b/share/images/upload_window_green.png differ diff --git a/share/locale/en.json b/share/locale/en.json index 50bb53c4..5322e86a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -183,5 +183,8 @@ "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", "systray_download_page_loaded_message": "A user loaded the download page", - "systray_upload_page_loaded_message": "A user loaded the upload page" + "systray_upload_page_loaded_message": "A user loaded the upload page", + "gui_uploads": "Upload History", + "gui_uploads_window_tooltip": "Show/hide uploads", + "gui_no_uploads": "No uploads yet." } -- cgit v1.2.3-54-g00ecf From a0db6d0ee77b4378cb5d5458f2b7a35e21cf4a34 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 18:08:23 -0700 Subject: Rename Downloads method names to remove the word "download" --- onionshare_gui/share_mode/__init__.py | 10 +++++----- onionshare_gui/share_mode/downloads.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 97d3d23f..41626b02 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -239,7 +239,7 @@ class ShareMode(Mode): """ Handle REQUEST_DOWNLOAD event. """ - self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) + self.downloads.add(event["data"]["id"], self.web.zip_filesize) self.downloads_in_progress += 1 self.update_downloads_in_progress() @@ -249,7 +249,7 @@ class ShareMode(Mode): """ Handle REQUEST_PROGRESS event. """ - self.downloads.update_download(event["data"]["id"], event["data"]["bytes"]) + self.downloads.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? if event["data"]["bytes"] == self.web.zip_filesize: @@ -269,7 +269,7 @@ class ShareMode(Mode): self.server_status_label.setText(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel_download(event["data"]["id"]) + self.downloads.cancel(event["data"]["id"]) self.downloads_in_progress = 0 self.update_downloads_in_progress() @@ -277,7 +277,7 @@ class ShareMode(Mode): """ Handle REQUEST_CANCELED event. """ - self.downloads.cancel_download(event["data"]["id"]) + self.downloads.cancel(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 @@ -338,7 +338,7 @@ class ShareMode(Mode): self.update_downloads_completed() self.update_downloads_in_progress() self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.downloads.reset_downloads() + self.downloads.reset() def update_downloads_completed(self): """ diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 5e267bbe..d4e1e8f0 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -126,7 +126,7 @@ class Downloads(QtWidgets.QScrollArea): widget.setLayout(layout) self.setWidget(widget) - def add_download(self, download_id, total_bytes): + def add(self, download_id, total_bytes): """ Add a new download progress bar. """ @@ -141,19 +141,19 @@ class Downloads(QtWidgets.QScrollArea): # Scroll to the bottom self.vbar.setValue(self.vbar.maximum()) - def update_download(self, download_id, downloaded_bytes): + def update(self, download_id, downloaded_bytes): """ Update the progress of a download progress bar. """ self.downloads[download_id].update(downloaded_bytes) - def cancel_download(self, download_id): + def cancel(self, download_id): """ Update a download progress bar to show that it has been canceled. """ self.downloads[download_id].cancel() - def reset_downloads(self): + def reset(self): """ Reset the downloads back to zero """ -- cgit v1.2.3-54-g00ecf From 23821ebae62097f6e8900f95a9eaefb9d1824be5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 15:44:04 -0700 Subject: Make ReceiveMode start using Uploads --- onionshare_gui/receive_mode/__init__.py | 25 +++++++++++++------------ onionshare_gui/receive_mode/uploads.py | 7 +++++++ onionshare_gui/share_mode/downloads.py | 20 +++++++++----------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index ac904600..ae8bf36d 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -87,15 +87,6 @@ class ReceiveMode(Mode): self.layout.insertWidget(0, self.receive_info) self.layout.insertWidget(0, self.info_widget) - def timer_callback_custom(self): - """ - This method is called regularly on a timer while share mode is active. - """ - # Scroll to the bottom of the download progress bar log pane if a new download has been added - #if self.new_download: - # self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum()) - # self.new_download = False - def get_stop_server_shutdown_timeout_text(self): """ Return the string to put on the stop server button, if there's a shutdown timeout @@ -116,9 +107,8 @@ class ReceiveMode(Mode): # Reset web counters self.web.error404_count = 0 - # Hide and reset the downloads if we have previously shared - #self.downloads.reset_downloads() - #self.reset_info_counters() + # Hide and reset the uploads if we have previously shared + self.reset_info_counters() def start_server_step2_custom(self): """ @@ -141,6 +131,17 @@ class ReceiveMode(Mode): self.stop_server() self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.uploads_completed = 0 + self.uploads_in_progress = 0 + self.update_uploads_completed() + self.update_uploads_in_progress() + self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png'))) + self.uploads.reset() + def update_uploads_completed(self): """ Update the 'Downloads completed' info widget. diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 445e9406..633806e9 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -21,6 +21,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings + class Uploads(QtWidgets.QScrollArea): """ The uploads chunk of the GUI. This lists all of the active upload @@ -55,3 +56,9 @@ class Uploads(QtWidgets.QScrollArea): layout.addStretch() widget.setLayout(layout) self.setWidget(widget) + + def reset(self): + """ + Reset the uploads back to zero + """ + pass diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index d4e1e8f0..f6f5b0e2 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -23,7 +23,6 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings class Download(object): - def __init__(self, common, download_id, total_bytes): self.common = common @@ -33,7 +32,14 @@ class Download(object): self.downloaded_bytes = 0 # make a new progress bar - cssStyleData =""" + 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(""" QProgressBar { border: 1px solid #4e064f; background-color: #ffffff !important; @@ -45,15 +51,7 @@ class Download(object): QProgressBar::chunk { background-color: #4e064f; width: 10px; - }""" - 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(cssStyleData) + }""") self.progress_bar.total_bytes = total_bytes # start at 0 -- cgit v1.2.3-54-g00ecf From 4d5f1a34cd6f4ce77e0f358323356a8697a1c02c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 16:21:22 -0700 Subject: Move all stylesheets definitions into Common, so now we no longer have blocks of css spread across the GUI code, and it's easier to re-use stylesheets --- onionshare/common.py | 189 ++++++++++++++++++++++++++++ onionshare_gui/__init__.py | 1 + onionshare_gui/onionshare_gui.py | 47 ++----- onionshare_gui/receive_mode/__init__.py | 4 +- onionshare_gui/receive_mode/uploads.py | 2 +- onionshare_gui/server_status.py | 19 ++- onionshare_gui/settings_dialog.py | 6 +- onionshare_gui/share_mode/__init__.py | 29 ++--- onionshare_gui/share_mode/downloads.py | 16 +-- onionshare_gui/share_mode/file_selection.py | 12 +- 10 files changed, 228 insertions(+), 97 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 87ec01b8..628064df 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -132,6 +132,195 @@ class Common(object): r = random.SystemRandom() return '-'.join(r.choice(wordlist) for _ in range(2)) + def define_css(self): + """ + This defines all of the stylesheets used in GUI mode, to avoid repeating code. + This method is only called in GUI mode. + """ + self.css = { + # OnionShareGui styles + 'mode_switcher_selected_style': """ + QPushButton { + color: #ffffff; + background-color: #4e064f; + border: 0; + border-right: 1px solid #69266b; + font-weight: bold; + border-radius: 0; + }""", + + 'mode_switcher_unselected_style': """ + QPushButton { + color: #ffffff; + background-color: #601f61; + border: 0; + font-weight: normal; + border-radius: 0; + }""", + + 'settings_button': """ + QPushButton { + background-color: #601f61; + border: 0; + border-left: 1px solid #69266b; + border-radius: 0; + }""", + + 'server_status_indicator_label': """ + QLabel { + font-style: italic; + color: #666666; + padding: 2px; + }""", + + 'status_bar': """ + QStatusBar { + font-style: italic; + color: #666666; + } + QStatusBar::item { + border: 0px; + }""", + + # Common styles between ShareMode and ReceiveMode and their child widgets + 'mode_info_label': """ + QLabel { + font-size: 12px; + color: #666666; + } + """, + + 'server_status_url': """ + QLabel { + background-color: #ffffff; + color: #000000; + padding: 10px; + border: 1px solid #666666; + } + """, + + 'server_status_url_buttons': """ + QPushButton { + color: #3f7fcf; + } + """, + + 'server_status_button_stopped': """ + QPushButton { + background-color: #5fa416; + color: #ffffff; + padding: 10px; + border: 0; + border-radius: 5px; + }""", + + 'server_status_button_working': """ + QPushButton { + background-color: #4c8211; + color: #ffffff; + padding: 10px; + border: 0; + border-radius: 5px; + font-style: italic; + }""", + + 'server_status_button_started': """ + QPushButton { + background-color: #d0011b; + color: #ffffff; + padding: 10px; + border: 0; + border-radius: 5px; + }""", + + 'downloads_uploads_label': """ + QLabel { + font-weight: bold; + font-size 14px; + text-align: center; + }""", + + 'downloads_uploads_progress_bar': """ + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + font-size: 12px; + } + QProgressBar::chunk { + background-color: #4e064f; + width: 10px; + }""", + + # Share mode and child widget styles + 'share_zip_progess_bar': """ + QProgressBar { + border: 1px solid #4e064f; + background-color: #ffffff !important; + text-align: center; + color: #9b9b9b; + } + QProgressBar::chunk { + border: 0px; + background-color: #4e064f; + width: 10px; + }""", + + 'share_filesize_warning': """ + QLabel { + padding: 10px 0; + font-weight: bold; + color: #333333; + } + """, + + 'share_file_selection_drop_here_label': """ + QLabel { + color: #999999; + }""", + + 'share_file_selection_drop_count_label': """ + QLabel { + color: #ffffff; + background-color: #f44449; + font-weight: bold; + padding: 5px 10px; + border-radius: 10px; + }""", + + 'share_file_list_drag_enter': """ + FileList { + border: 3px solid #538ad0; + } + """, + + 'share_file_list_drag_leave': """ + FileList { + border: none; + } + """, + + 'share_file_list_item_size': """ + QLabel { + color: #666666; + font-size: 11px; + }""", + + # Settings dialog + 'settings_version': """ + QLabel { + color: #666666; + }""", + + 'settings_tor_status': """ + QLabel { + background-color: #ffffff; + color: #000000; + padding: 10px; + }""" + } + @staticmethod def random_string(num_bytes, output_len=None): """ diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 6254676c..13f0e8c7 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -53,6 +53,7 @@ def main(): The main() function implements all of the logic that the GUI version of onionshare uses. """ common = Common() + common.define_css() strings.load_strings(common) print(strings._('version_string').format(common.version)) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8db05257..81bea23e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -80,23 +80,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.system_tray.show() # Mode switcher, to switch between share files and receive files - self.mode_switcher_selected_style = """ - QPushButton { - color: #ffffff; - background-color: #4e064f; - border: 0; - border-right: 1px solid #69266b; - font-weight: bold; - border-radius: 0; - }""" - self.mode_switcher_unselected_style = """ - QPushButton { - color: #ffffff; - background-color: #601f61; - border: 0; - font-weight: normal; - border-radius: 0; - }""" self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True)); self.share_mode_button.setFixedHeight(50) self.share_mode_button.clicked.connect(self.share_mode_clicked) @@ -109,13 +92,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_button.setFixedHeight(50) self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) ) self.settings_button.clicked.connect(self.open_settings) - self.settings_button.setStyleSheet(""" - QPushButton { - background-color: #601f61; - border: 0; - border-left: 1px solid #69266b; - border-radius: 0; - }""") + self.settings_button.setStyleSheet(self.common.css['settings_button']) mode_switcher_layout = QtWidgets.QHBoxLayout(); mode_switcher_layout.setSpacing(0) mode_switcher_layout.addWidget(self.share_mode_button) @@ -129,7 +106,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.server_status_image_label = QtWidgets.QLabel() self.server_status_image_label.setFixedWidth(20) self.server_status_label = QtWidgets.QLabel('') - self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }') + self.server_status_label.setStyleSheet(self.common.css['server_status_indicator_label']) server_status_indicator_layout = QtWidgets.QHBoxLayout() server_status_indicator_layout.addWidget(self.server_status_image_label) server_status_indicator_layout.addWidget(self.server_status_label) @@ -139,15 +116,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Status bar self.status_bar = QtWidgets.QStatusBar() self.status_bar.setSizeGripEnabled(False) - self.status_bar.setStyleSheet(""" - QStatusBar { - font-style: italic; - color: #666666; - } - - QStatusBar::item { - border: 0px; - }""") + self.status_bar.setStyleSheet(self.common.css['status_bar']) self.status_bar.addPermanentWidget(self.server_status_indicator) self.setStatusBar(self.status_bar) @@ -223,14 +192,14 @@ class OnionShareGui(QtWidgets.QMainWindow): # Based on the current mode, switch the mode switcher button styles, # and show and hide widgets to switch modes if self.mode == self.MODE_SHARE: - self.share_mode_button.setStyleSheet(self.mode_switcher_selected_style) - self.receive_mode_button.setStyleSheet(self.mode_switcher_unselected_style) + self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) + self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.share_mode.show() self.receive_mode.hide() else: - self.share_mode_button.setStyleSheet(self.mode_switcher_unselected_style) - self.receive_mode_button.setStyleSheet(self.mode_switcher_selected_style) + self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) + self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.share_mode.hide() self.receive_mode.show() @@ -414,7 +383,7 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_CANCELED: mode.handle_request_canceled(event) - + elif event["type"] == Web.REQUEST_CLOSE_SERVER: mode.handle_request_close_server(event) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index ae8bf36d..9099dec9 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -60,10 +60,10 @@ class ReceiveMode(Mode): self.info_show_uploads.setToolTip(strings._('gui_uploads_window_tooltip', True)) self.info_in_progress_uploads_count = QtWidgets.QLabel() - self.info_in_progress_uploads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) self.info_completed_uploads_count = QtWidgets.QLabel() - self.info_completed_uploads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) self.update_uploads_completed() self.update_uploads_in_progress() diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 633806e9..b611e37a 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -43,7 +43,7 @@ class Uploads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) - uploads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True)) self.uploads_layout = QtWidgets.QVBoxLayout() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index ee60f432..1562ee10 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -90,16 +90,15 @@ class ServerStatus(QtWidgets.QWidget): self.url.setWordWrap(True) self.url.setMinimumHeight(60) self.url.setMinimumSize(self.url.sizeHint()) - self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') + self.url.setStyleSheet(self.common.css['server_status_url']) - url_buttons_style = 'QPushButton { color: #3f7fcf; }' self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) self.copy_url_button.setFlat(True) - self.copy_url_button.setStyleSheet(url_buttons_style) + self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) self.copy_url_button.clicked.connect(self.copy_url) self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.copy_hidservauth_button.setFlat(True) - self.copy_hidservauth_button.setStyleSheet(url_buttons_style) + self.copy_hidservauth_button.setStyleSheet(self.common.css['server_status_url_buttons']) self.copy_hidservauth_button.clicked.connect(self.copy_hidservauth) url_buttons_layout = QtWidgets.QHBoxLayout() url_buttons_layout.addWidget(self.copy_url_button) @@ -187,17 +186,13 @@ class ServerStatus(QtWidgets.QWidget): self.copy_hidservauth_button.hide() # Button - button_stopped_style = 'QPushButton { background-color: #5fa416; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - button_working_style = 'QPushButton { background-color: #4c8211; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; font-style: italic; }' - button_started_style = 'QPushButton { background-color: #d0011b; color: #ffffff; padding: 10px; border: 0; border-radius: 5px; }' - if self.mode == ServerStatus.MODE_SHARE and self.file_selection.get_num_files() == 0: self.server_button.hide() else: self.server_button.show() if self.status == self.STATUS_STOPPED: - self.server_button.setStyleSheet(button_stopped_style) + self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_start_server', True)) @@ -207,7 +202,7 @@ class ServerStatus(QtWidgets.QWidget): if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() elif self.status == self.STATUS_STARTED: - self.server_button.setStyleSheet(button_started_style) + self.server_button.setStyleSheet(self.common.css['server_status_button_started']) self.server_button.setEnabled(True) if self.mode == ServerStatus.MODE_SHARE: self.server_button.setText(strings._('gui_share_stop_server', True)) @@ -221,13 +216,13 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setToolTip(strings._('gui_receive_stop_server_shutdown_timeout_tooltip', True).format(self.timeout)) elif self.status == self.STATUS_WORKING: - self.server_button.setStyleSheet(button_working_style) + self.server_button.setStyleSheet(self.common.css['server_status_button_working']) self.server_button.setEnabled(True) self.server_button.setText(strings._('gui_please_wait')) if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.hide() else: - self.server_button.setStyleSheet(button_working_style) + self.server_button.setStyleSheet(self.common.css['server_status_button_working']) self.server_button.setEnabled(False) self.server_button.setText(strings._('gui_please_wait')) if self.common.settings.get('shutdown_timeout'): diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index da5be7fc..94480205 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -360,7 +360,7 @@ class SettingsDialog(QtWidgets.QDialog): self.cancel_button = QtWidgets.QPushButton(strings._('gui_settings_button_cancel', True)) self.cancel_button.clicked.connect(self.cancel_clicked) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.version)) - version_label.setStyleSheet('color: #666666') + version_label.setStyleSheet(self.common.css['settings_version']) self.help_button = QtWidgets.QPushButton(strings._('gui_settings_button_help', True)) self.help_button.clicked.connect(self.help_clicked) buttons_layout = QtWidgets.QHBoxLayout() @@ -372,7 +372,7 @@ class SettingsDialog(QtWidgets.QDialog): # Tor network connection status self.tor_status = QtWidgets.QLabel() - self.tor_status.setStyleSheet('background-color: #ffffff; color: #000000; padding: 10px') + self.tor_status.setStyleSheet(self.common.css['settings_tor_status']) self.tor_status.hide() # Layout @@ -430,7 +430,7 @@ class SettingsDialog(QtWidgets.QDialog): self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked) else: self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked) - + receive_public_mode = self.old_settings.get('receive_public_mode') if receive_public_mode: self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 41626b02..b87b515b 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -64,7 +64,7 @@ class ShareMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;') + self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) self.filesize_warning.hide() # Downloads @@ -74,7 +74,7 @@ class ShareMode(Mode): # Information about share, and show downloads button self.info_label = QtWidgets.QLabel() - self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_label.setStyleSheet(self.common.css['mode_info_label']) self.info_show_downloads = QtWidgets.QToolButton() self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) @@ -83,10 +83,10 @@ class ShareMode(Mode): self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) self.info_in_progress_downloads_count = QtWidgets.QLabel() - self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) self.info_completed_downloads_count = QtWidgets.QLabel() - self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }') + self.info_completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) self.update_downloads_completed() self.update_downloads_in_progress() @@ -153,7 +153,7 @@ class ShareMode(Mode): Step 2 in starting the server. Zipping up files. """ # Add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(0) + self._zip_progress_bar = ZipProgressBar(self.common, 0) self.filenames = [] for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) @@ -377,26 +377,15 @@ class ShareMode(Mode): class ZipProgressBar(QtWidgets.QProgressBar): update_processed_size_signal = QtCore.pyqtSignal(int) - def __init__(self, total_files_size): + def __init__(self, common, total_files_size): super(ZipProgressBar, self).__init__() + self.common = common + self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) self.setFormat(strings._('zip_progress_bar_format')) - cssStyleData =""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - } - - QProgressBar::chunk { - border: 0px; - background-color: #4e064f; - width: 10px; - }""" - self.setStyleSheet(cssStyleData) + self.setStyleSheet(self.common.css['share_zip_progess_bar']) self._total_files_size = total_files_size self._processed_size = 0 diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index f6f5b0e2..4af18544 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -39,19 +39,7 @@ class Download(object): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(""" - QProgressBar { - border: 1px solid #4e064f; - background-color: #ffffff !important; - text-align: center; - color: #9b9b9b; - font-size: 12px; - } - - QProgressBar::chunk { - background-color: #4e064f; - width: 10px; - }""") + self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) self.progress_bar.total_bytes = total_bytes # start at 0 @@ -110,7 +98,7 @@ class Downloads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) self.downloads_layout = QtWidgets.QVBoxLayout() diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/share_mode/file_selection.py index aa50719b..628ad5ef 100644 --- a/onionshare_gui/share_mode/file_selection.py +++ b/onionshare_gui/share_mode/file_selection.py @@ -42,7 +42,7 @@ class DropHereLabel(QtWidgets.QLabel): self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) else: self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet('color: #999999;') + self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) self.hide() @@ -66,7 +66,7 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet('color: #ffffff; background-color: #f44449; font-weight: bold; padding: 5px 10px; border-radius: 10px;') + self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) self.hide() def dragEnterEvent(self, event): @@ -158,7 +158,7 @@ class FileList(QtWidgets.QListWidget): dragEnterEvent for dragging files and directories into the widget. """ if event.mimeData().hasUrls: - self.setStyleSheet('FileList { border: 3px solid #538ad0; }') + self.setStyleSheet(self.common.css['share_file_list_drag_enter']) count = len(event.mimeData().urls()) self.drop_count.setText('+{}'.format(count)) @@ -173,7 +173,7 @@ class FileList(QtWidgets.QListWidget): """ dragLeaveEvent for dragging files and directories into the widget. """ - self.setStyleSheet('FileList { border: none; }') + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) self.drop_count.hide() event.accept() self.update() @@ -201,7 +201,7 @@ class FileList(QtWidgets.QListWidget): else: event.ignore() - self.setStyleSheet('border: none;') + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) self.drop_count.hide() self.files_dropped.emit() @@ -238,7 +238,7 @@ class FileList(QtWidgets.QListWidget): # Item's filename attribute and size labels item.filename = filename item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }') + item_size.setStyleSheet(self.common.css['share_file_list_item_size']) item.basename = os.path.basename(filename.rstrip('/')) # Use the basename as the method with which to sort the list -- cgit v1.2.3-54-g00ecf From 996f6c072579a8a1f81aa76dae11809b57fbbcc3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 16:38:29 -0700 Subject: Create an Upload class within Uploads, and add methods to Uploads to add, update, cancel, and reset --- onionshare_gui/receive_mode/uploads.py | 101 ++++++++++++++++++++++++++++++++- onionshare_gui/share_mode/downloads.py | 11 ++-- share/locale/cs.json | 6 +- share/locale/da.json | 6 +- share/locale/en.json | 6 +- share/locale/eo.json | 6 +- share/locale/nl.json | 6 +- 7 files changed, 121 insertions(+), 21 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index b611e37a..8d4712a3 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -22,6 +22,72 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +class Upload(object): + def __init__(self, common, upload_id, total_bytes): + self.common = common + + self.upload_id = upload_id + self.started = time.time() + self.total_bytes = total_bytes + self.uploaded_bytes = 0 + + # Uploads have two modes, in progress and finished. In progess, they display + # the progress bar. When finished, they display info about the files that + # were uploaded. + + # 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 + + # Finished + self.finished = QtWidgets.QGroupBox() + self.finished.hide() + + # Start at 0 + self.update(0) + + def update(self, uploaded_bytes): + self.uploaded_bytes = uploaded_bytes + + self.progress_bar.setValue(uploaded_bytes) + if uploaded_bytes == self.progress_bar.uploaded_bytes: + # Upload is finished, hide the progress bar and show the finished widget + self.progress_bar.hide() + + # TODO: add file information to the finished widget + ended = time.time() + elapsed = ended - self.started + self.finished.show() + + else: + elapsed = time.time() - self.started + if elapsed < 10: + 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.uploaded_bytes, + self.total_bytes, + self.started) + + class Uploads(QtWidgets.QScrollArea): """ The uploads chunk of the GUI. This lists all of the active upload @@ -57,8 +123,41 @@ class Uploads(QtWidgets.QScrollArea): widget.setLayout(layout) self.setWidget(widget) + def add(self, upload_id, total_bytes): + """ + Add a new upload progress bar. + """ + # Hide the no_uploads_label + self.no_uploads_label.hide() + + # Add it to the list + uploads = Upload(self.common, upload_id, total_bytes) + self.uploads[upload_id] = download + self.uploads_layout.addWidget(upload.progress_bar) + + # Scroll to the bottom + self.vbar.setValue(self.vbar.maximum()) + + def update(self, upload_id, uploaded_bytes): + """ + Update the progress of an upload progress bar. + """ + self.uploads[upload_id].update(uploaded_bytes) + + def cancel(self, upload_id): + """ + Update an upload progress bar to show that it has been canceled. + """ + self.uploads[upload_id].cancel() + def reset(self): """ Reset the uploads back to zero """ - pass + for upload in self.uploads.values(): + self.uploads_layout.removeWidget(upload.progress_bar) + upload.progress_bar.close() + self.uploads = {} + + self.no_uploads_label.show() + self.resize(self.sizeHint()) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 4af18544..f5e8512e 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -22,6 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings + class Download(object): def __init__(self, common, download_id, total_bytes): self.common = common @@ -31,7 +32,7 @@ class Download(object): self.total_bytes = total_bytes self.downloaded_bytes = 0 - # make a new progress bar + # Progress bar self.progress_bar = QtWidgets.QProgressBar() self.progress_bar.setTextVisible(True) self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -42,7 +43,7 @@ class Download(object): self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) self.progress_bar.total_bytes = total_bytes - # start at 0 + # Start at 0 self.update(0) def update(self, downloaded_bytes): @@ -50,7 +51,7 @@ class Download(object): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_download_progress_complete').format( + pb_fmt = strings._('gui_download_upload_progress_complete').format( self.common.format_seconds(time.time() - self.started)) else: elapsed = time.time() - self.started @@ -58,10 +59,10 @@ class Download(object): # 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_progress_starting').format( + pb_fmt = strings._('gui_download_upload_progress_starting').format( self.common.human_readable_filesize(downloaded_bytes)) else: - pb_fmt = strings._('gui_download_progress_eta').format( + pb_fmt = strings._('gui_download_upload_progress_eta').format( self.common.human_readable_filesize(downloaded_bytes), self.estimated_time_remaining) diff --git a/share/locale/cs.json b/share/locale/cs.json index 233a156b..aaa80d1b 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -39,9 +39,9 @@ "error_hs_dir_cannot_create": "Nejde vytvořit složka onion service {0:s}", "error_hs_dir_not_writable": "nejde zapisovat do složky onion service {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_progress_complete": "%p%, Uplynulý čas: {0:s}", - "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", diff --git a/share/locale/da.json b/share/locale/da.json index 12db70ae..d36f7035 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -52,9 +52,9 @@ "error_hs_dir_cannot_create": "Kan ikke oprette onion-tjenestens mappe {0:s}", "error_hs_dir_not_writable": "onion-tjenestens mappe {0:s} er skrivebeskyttet", "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse", - "gui_download_progress_complete": "%p%, tid forløbet: {0:s}", - "gui_download_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", - "gui_download_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", + "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", + "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", "gui_quit_warning_quit": "Afslut", diff --git a/share/locale/en.json b/share/locale/en.json index 5322e86a..40f30566 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -64,9 +64,9 @@ "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_progress_complete": "%p%, Time Elapsed: {0:s}", - "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Transfer in Progress", "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", diff --git a/share/locale/eo.json b/share/locale/eo.json index 90b7e9c7..0745ecaf 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -39,9 +39,9 @@ "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_progress_complete": "%p%, Tempo pasinta: {0:s}", - "gui_download_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.", "gui_quit_warning_quit": "Foriri", diff --git a/share/locale/nl.json b/share/locale/nl.json index 062635d2..4031effd 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -50,9 +50,9 @@ "error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken", "error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar", "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie", - "gui_download_progress_complete": "%p%, Tijd verstreken: {0:s}", - "gui_download_progress_starting": "{0:s}, %p% (ETA berekenen)", - "gui_download_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", -- cgit v1.2.3-54-g00ecf From 591e97a57a33e6b7364cd81a34b9148fdda816e1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 22:15:29 -0700 Subject: Make receive mode events just like share mode, and rename REQUEST_DOWNLOAD to REQUEST_SHARE --- onionshare/web.py | 58 ++++++++++++++++++++++++--------- onionshare_gui/mode.py | 2 +- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/receive_mode/__init__.py | 7 ++++ onionshare_gui/share_mode/__init__.py | 2 +- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 39bb6f34..8fe46bcc 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -45,13 +45,13 @@ class Web(object): The Web object is the OnionShare web server, powered by flask """ REQUEST_LOAD = 0 - REQUEST_DOWNLOAD = 1 + REQUEST_STARTED = 1 REQUEST_PROGRESS = 2 REQUEST_OTHER = 3 REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 5 REQUEST_CLOSE_SERVER = 6 - + def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -103,6 +103,7 @@ class Web(object): self.slug = None self.download_count = 0 + self.upload_count = 0 self.error404_count = 0 # If "Stop After First Download" is checked (stay_open == False), only allow @@ -173,17 +174,17 @@ class Web(object): r = make_response(render_template('denied.html')) return self.add_security_headers(r) - # each download has a unique id + # Each download has a unique id download_id = self.download_count self.download_count += 1 - # prepare some variables to use inside generate() function below + # Prepare some variables to use inside generate() function below # which is outside of the request context shutdown_func = request.environ.get('werkzeug.server.shutdown') path = request.path - # tell GUI the download started - self.add_request(self.REQUEST_DOWNLOAD, path, {'id': download_id}) + # Tell GUI the download started + self.add_request(self.REQUEST_STARTED, path, {'id': download_id}) dirname = os.path.dirname(self.zip_filename) basename = os.path.basename(self.zip_filename) @@ -266,9 +267,8 @@ class Web(object): def receive_routes(self): """ - The web app routes for sharing files + The web app routes for receiving files """ - def index_logic(): self.add_request(self.REQUEST_LOAD, request.path) @@ -277,12 +277,12 @@ class Web(object): slug=self.slug, receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) return self.add_security_headers(r) - + @self.app.route("/") def index(slug_candidate): self.check_slug_candidate(slug_candidate) return index_logic() - + @self.app.route("/") def index_public(): if not self.common.settings.get('receive_public_mode'): @@ -291,6 +291,9 @@ class Web(object): def upload_logic(slug_candidate=''): + """ + Upload files. + """ files = request.files.getlist('file[]') filenames = [] for f in files: @@ -345,7 +348,7 @@ class Web(object): def upload(slug_candidate): self.check_slug_candidate(slug_candidate) return upload_logic(slug_candidate) - + @self.app.route("/upload") def upload_public(): if not self.common.settings.get('receive_public_mode'): @@ -361,12 +364,12 @@ class Web(object): return self.add_security_headers(r) else: return redirect('/{}'.format(slug_candidate)) - + @self.app.route("//close", methods=['POST']) def close(slug_candidate): self.check_slug_candidate(slug_candidate) return close_logic(slug_candidate) - + @self.app.route("/upload") def close_public(): if not self.common.settings.get('receive_public_mode'): @@ -653,9 +656,24 @@ class ReceiveModeRequest(Request): This gets called for each file that gets uploaded, and returns an file-like writable stream. """ + # Each upload has a unique id. Now that the upload is starting, attach its + # upload_id to the request + self.upload_id = self.web.upload_count + self.web.upload_count += 1 + + # Tell GUI the upload started + self.web.add_request(self.web.REQUEST_STARTED, self.path, { + 'id': self.upload_id + }) + + self.onionshare_progress[filename] = { + 'total_bytes': total_content_length, + 'uploaded_bytes': 0 + } + if len(self.onionshare_progress) > 0: print('') - self.onionshare_progress[filename] = 0 + return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) def close(self): @@ -670,5 +688,13 @@ class ReceiveModeRequest(Request): """ Keep track of the bytes uploaded so far for all files. """ - self.onionshare_progress[filename] += length - print('{} - {} '.format(self.web.common.human_readable_filesize(self.onionshare_progress[filename]), filename), end='\r') + self.onionshare_progress[filename]['uploaded_bytes'] += length + uploaded = self.web.common.human_readable_filesize(self.onionshare_progress[filename]['uploaded_bytes']) + total = self.web.common.human_readable_filesize(self.onionshare_progress[filename]['total_bytes']) + print('{}/{} - {} '.format(uploaded, total, filename), end='\r') + + # Update the GUI on the download progress + self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'bytes': self.onionshare_progress[filename]['uploaded_bytes'] + }) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index d425341e..1e8bd23b 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -295,7 +295,7 @@ class Mode(QtWidgets.QWidget): def handle_request_download(self, event): """ - Handle REQUEST_DOWNLOAD event. + Handle REQUEST_STARTED event. """ pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 81bea23e..295db45b 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -372,7 +372,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if event["type"] == Web.REQUEST_LOAD: mode.handle_request_load(event) - elif event["type"] == Web.REQUEST_DOWNLOAD: + elif event["type"] == Web.REQUEST_STARTED: mode.handle_request_download(event) elif event["type"] == Web.REQUEST_RATE_LIMIT: diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 9099dec9..6fd0031c 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -105,6 +105,7 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters + self.web.upload_count = 0 self.web.error404_count = 0 # Hide and reset the uploads if we have previously shared @@ -118,6 +119,12 @@ class ReceiveMode(Mode): self.starting_server_step3.emit() self.start_server_finished.emit() + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.info_widget.hide() + def handle_request_load(self, event): """ Handle REQUEST_LOAD event. diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index b87b515b..12a9265a 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -237,7 +237,7 @@ class ShareMode(Mode): def handle_request_download(self, event): """ - Handle REQUEST_DOWNLOAD event. + Handle REQUEST_STARTED event. """ self.downloads.add(event["data"]["id"], self.web.zip_filesize) self.downloads_in_progress += 1 -- cgit v1.2.3-54-g00ecf From 9d557d4aa0dc75f5033cb980b4819bcfbae70a7f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 22:16:45 -0700 Subject: Renamed Mode.handle_request_download to handle_request_started --- onionshare_gui/mode.py | 2 +- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/share_mode/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 1e8bd23b..3cfa4b0c 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -293,7 +293,7 @@ class Mode(QtWidgets.QWidget): """ pass - def handle_request_download(self, event): + def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 295db45b..9e9d8583 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -373,7 +373,7 @@ class OnionShareGui(QtWidgets.QMainWindow): mode.handle_request_load(event) elif event["type"] == Web.REQUEST_STARTED: - mode.handle_request_download(event) + mode.handle_request_started(event) elif event["type"] == Web.REQUEST_RATE_LIMIT: mode.handle_request_rate_limit(event) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 12a9265a..37315bbe 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -235,7 +235,7 @@ class ShareMode(Mode): """ self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) - def handle_request_download(self, event): + def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ -- cgit v1.2.3-54-g00ecf From eb3d6f217164098762ff6cc2a7d4d42aad6c319a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 23:07:11 -0700 Subject: Start making Web events actually put Upload objects into Uploads --- onionshare/web.py | 92 +++++++++++++++++++++++---------- onionshare_gui/mode.py | 12 +++++ onionshare_gui/onionshare_gui.py | 6 +++ onionshare_gui/receive_mode/__init__.py | 30 +++++++++++ onionshare_gui/receive_mode/uploads.py | 86 ++++++++---------------------- share/locale/en.json | 8 ++- 6 files changed, 142 insertions(+), 92 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 8fe46bcc..63a0fcb5 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -51,6 +51,8 @@ class Web(object): REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 5 REQUEST_CLOSE_SERVER = 6 + REQUEST_UPLOAD_NEW_FILE_STARTED = 7 + REQUEST_UPLOAD_FILE_RENAMED = 8 def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -104,6 +106,7 @@ class Web(object): self.download_count = 0 self.upload_count = 0 + self.error404_count = 0 # If "Stop After First Download" is checked (stay_open == False), only allow @@ -141,7 +144,7 @@ class Web(object): """ self.check_slug_candidate(slug_candidate) - self.add_request(self.REQUEST_LOAD, request.path) + self.add_request(Web.REQUEST_LOAD, request.path) # Deny new downloads if "Stop After First Download" is checked and there is # currently a download @@ -184,7 +187,9 @@ class Web(object): path = request.path # Tell GUI the download started - self.add_request(self.REQUEST_STARTED, path, {'id': download_id}) + self.add_request(Web.REQUEST_STARTED, path, { + 'id': download_id} + ) dirname = os.path.dirname(self.zip_filename) basename = os.path.basename(self.zip_filename) @@ -205,7 +210,9 @@ class Web(object): while not self.done: # The user has canceled the download, so stop serving the file if self.client_cancel: - self.add_request(self.REQUEST_CANCELED, path, {'id': download_id}) + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) break chunk = fp.read(chunk_size) @@ -225,7 +232,10 @@ class Web(object): "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) sys.stdout.flush() - self.add_request(self.REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes}) + self.add_request(Web.REQUEST_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) self.done = False except: # looks like the download was canceled @@ -233,7 +243,9 @@ class Web(object): canceled = True # tell the GUI the download has canceled - self.add_request(self.REQUEST_CANCELED, path, {'id': download_id}) + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) fp.close() @@ -270,7 +282,7 @@ class Web(object): The web app routes for receiving files """ def index_logic(): - self.add_request(self.REQUEST_LOAD, request.path) + self.add_request(Web.REQUEST_LOAD, request.path) r = make_response(render_template( 'receive.html', @@ -330,6 +342,15 @@ class Web(object): else: valid = True + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) print(strings._('receive_mode_received_file').format(local_path)) f.save(local_path) @@ -360,7 +381,7 @@ class Web(object): if self.common.settings.get('receive_allow_receiver_shutdown'): self.force_shutdown() r = make_response(render_template('closed.html')) - self.add_request(self.REQUEST_CLOSE_SERVER, request.path) + self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) return self.add_security_headers(r) else: return redirect('/{}'.format(slug_candidate)) @@ -397,14 +418,14 @@ class Web(object): return "" def error404(self): - self.add_request(self.REQUEST_OTHER, request.path) + self.add_request(Web.REQUEST_OTHER, request.path) if request.path != '/favicon.ico': self.error404_count += 1 # In receive mode, with public mode enabled, skip rate limiting 404s if not (self.receive_mode and self.common.settings.get('receive_public_mode')): if self.error404_count == 20: - self.add_request(self.REQUEST_RATE_LIMIT, request.path) + self.add_request(Web.REQUEST_RATE_LIMIT, request.path) self.force_shutdown() print(strings._('error_rate_limit')) @@ -610,6 +631,7 @@ class ReceiveModeWSGIMiddleware(object): environ['web'] = self.web return self.app(environ, start_response) + class ReceiveModeTemporaryFile(object): """ A custom TemporaryFile that tells ReceiveModeRequest every time data gets @@ -649,29 +671,45 @@ class ReceiveModeRequest(Request): self.web = environ['web'] # A dictionary that maps filenames to the bytes uploaded so far - self.onionshare_progress = {} + self.progress = {} + + # Is this a valid upload request? + self.upload_request = False + if self.method == 'POST': + if self.web.common.settings.get('receive_public_mode'): + if self.path == '/upload': + self.upload_request = True + else: + if self.path == '/{}/upload'.format(self.web.slug): + self.upload_request = True + + # If this is an upload request, create an upload_id (attach it to the request) + self.upload_id = self.web.upload_count + self.web.upload_count += 1 + + # Tell the GUI + self.web.add_request(Web.REQUEST_STARTED, self.path, { + 'id': self.upload_id + }) def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): """ This gets called for each file that gets uploaded, and returns an file-like writable stream. """ - # Each upload has a unique id. Now that the upload is starting, attach its - # upload_id to the request - self.upload_id = self.web.upload_count - self.web.upload_count += 1 - - # Tell GUI the upload started - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.upload_id + # Tell the GUI about the new file upload + self.web.add_request(Web.REQUEST_UPLOAD_NEW_FILE_STARTED, self.path, { + 'id': self.upload_id, + 'filename': filename, + 'total_bytes': total_content_length }) - self.onionshare_progress[filename] = { + self.progress[filename] = { 'total_bytes': total_content_length, 'uploaded_bytes': 0 } - if len(self.onionshare_progress) > 0: + if len(self.progress) > 0: print('') return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) @@ -681,20 +719,20 @@ class ReceiveModeRequest(Request): When closing the request, print a newline if this was a file upload. """ super(ReceiveModeRequest, self).close() - if len(self.onionshare_progress) > 0: + if len(self.progress) > 0: print('') def onionshare_update_func(self, filename, length): """ Keep track of the bytes uploaded so far for all files. """ - self.onionshare_progress[filename]['uploaded_bytes'] += length - uploaded = self.web.common.human_readable_filesize(self.onionshare_progress[filename]['uploaded_bytes']) - total = self.web.common.human_readable_filesize(self.onionshare_progress[filename]['total_bytes']) + self.progress[filename]['uploaded_bytes'] += length + uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) + total = self.web.common.human_readable_filesize(self.progress[filename]['total_bytes']) print('{}/{} - {} '.format(uploaded, total, filename), end='\r') - # Update the GUI on the download progress - self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { + # Update the GUI on the upload progress + self.web.add_request(Web.REQUEST_PROGRESS, self.path, { 'id': self.upload_id, - 'bytes': self.onionshare_progress[filename]['uploaded_bytes'] + 'progress': self.progress }) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 3cfa4b0c..edcedca3 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -323,3 +323,15 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_CLOSE_SERVER event. """ pass + + def handle_request_upload_new_file_started(self, event): + """ + Handle REQUEST_UPLOAD_NEW_FILE_STARTED event. + """ + pass + + def handle_request_upload_file_renamed(self, event): + """ + Handle REQUEST_UPLOAD_FILE_RENAMED event. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 9e9d8583..893d2dae 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -387,6 +387,12 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_CLOSE_SERVER: mode.handle_request_close_server(event) + elif event["type"] == Web.REQUEST_UPLOAD_NEW_FILE_STARTED: + mode.handle_request_upload_new_file_started(event) + + elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: + mode.handle_request_upload_file_renamed(event) + elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 6fd0031c..000850d2 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -131,6 +131,24 @@ class ReceiveMode(Mode): """ self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + self.uploads.add(event["data"]["id"]) + self.uploads_in_progress += 1 + self.update_uploads_in_progress() + + self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.uploads.update(event["data"]["id"], event["data"]["progress"]) + + # TODO: not done yet + def handle_request_close_server(self, event): """ Handle REQUEST_CLOSE_SERVER event. @@ -138,6 +156,18 @@ class ReceiveMode(Mode): self.stop_server() self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) + def handle_request_upload_new_file_started(self, event): + """ + Handle REQUEST_UPLOAD_NEW_FILE_STARTED event. + """ + pass + + def handle_request_upload_file_renamed(self, event): + """ + Handle REQUEST_UPLOAD_FILE_RENAMED event. + """ + pass + def reset_info_counters(self): """ Set the info counters back to zero. diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 8d4712a3..a36f2364 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -17,75 +17,33 @@ 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 . """ +from datetime import datetime from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -class Upload(object): - def __init__(self, common, upload_id, total_bytes): +class Upload(QtWidgets.QGroupBox): + def __init__(self, common, upload_id): + super(Upload, self).__init__() self.common = common self.upload_id = upload_id - self.started = time.time() - self.total_bytes = total_bytes + self.started = datetime.now() self.uploaded_bytes = 0 - # Uploads have two modes, in progress and finished. In progess, they display - # the progress bar. When finished, they display info about the files that - # were uploaded. - - # 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 - - # Finished - self.finished = QtWidgets.QGroupBox() - self.finished.hide() + # Set the title of the title of the group box based on the start time + self.setTitle(strings._('gui_upload_in_progress', True).format(self.started.strftime("%b %m, %I:%M%p"))) # Start at 0 - self.update(0) + self.update({}) - def update(self, uploaded_bytes): - self.uploaded_bytes = uploaded_bytes - - self.progress_bar.setValue(uploaded_bytes) - if uploaded_bytes == self.progress_bar.uploaded_bytes: - # Upload is finished, hide the progress bar and show the finished widget - self.progress_bar.hide() - - # TODO: add file information to the finished widget - ended = time.time() - elapsed = ended - self.started - self.finished.show() - - else: - elapsed = time.time() - self.started - if elapsed < 10: - 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.uploaded_bytes, - self.total_bytes, - self.started) + def update(self, progress): + """ + Using the progress from Web, make sure all the file progress bars exist, + and update their progress + """ + pass class Uploads(QtWidgets.QScrollArea): @@ -123,17 +81,17 @@ class Uploads(QtWidgets.QScrollArea): widget.setLayout(layout) self.setWidget(widget) - def add(self, upload_id, total_bytes): + def add(self, upload_id): """ - Add a new upload progress bar. + Add a new upload. """ # Hide the no_uploads_label self.no_uploads_label.hide() # Add it to the list - uploads = Upload(self.common, upload_id, total_bytes) - self.uploads[upload_id] = download - self.uploads_layout.addWidget(upload.progress_bar) + upload = Upload(self.common, upload_id) + self.uploads[upload_id] = upload + self.uploads_layout.addWidget(upload) # Scroll to the bottom self.vbar.setValue(self.vbar.maximum()) @@ -142,7 +100,8 @@ class Uploads(QtWidgets.QScrollArea): """ Update the progress of an upload progress bar. """ - self.uploads[upload_id].update(uploaded_bytes) + pass + #self.uploads[upload_id].update(uploaded_bytes) def cancel(self, upload_id): """ @@ -155,8 +114,7 @@ class Uploads(QtWidgets.QScrollArea): Reset the uploads back to zero """ for upload in self.uploads.values(): - self.uploads_layout.removeWidget(upload.progress_bar) - upload.progress_bar.close() + self.uploads_layout.removeWidget(upload) self.uploads = {} self.no_uploads_label.show() diff --git a/share/locale/en.json b/share/locale/en.json index 40f30566..4b8c2c04 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -28,6 +28,10 @@ "systray_download_completed_message": "The user finished downloading your files", "systray_download_canceled_title": "OnionShare Download Canceled", "systray_download_canceled_message": "The user canceled the download", + "systray_upload_started_title": "OnionShare Upload Started", + "systray_upload_started_message": "A user started uploading files to your computer", + "systray_upload_completed_title": "OnionShare Upload Finished", + "systray_upload_completed_message": "The user finished uploading files to your computer", "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", @@ -186,5 +190,7 @@ "systray_upload_page_loaded_message": "A user loaded the upload page", "gui_uploads": "Upload History", "gui_uploads_window_tooltip": "Show/hide uploads", - "gui_no_uploads": "No uploads yet." + "gui_no_uploads": "No uploads yet.", + "gui_upload_in_progress": "Upload in progress, started {}", + "gui_upload_finished": "Uploaded {} to {}" } -- cgit v1.2.3-54-g00ecf From 841e47b2349a6d7aec717ed4f785a244dc6a5d13 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 8 May 2018 13:35:50 -0700 Subject: ReceiveModeRequest should only deal with upload_ids for upload requests, not for other requests --- onionshare/web.py | 72 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 63a0fcb5..282ddd81 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -670,9 +670,6 @@ class ReceiveModeRequest(Request): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) self.web = environ['web'] - # A dictionary that maps filenames to the bytes uploaded so far - self.progress = {} - # Is this a valid upload request? self.upload_request = False if self.method == 'POST': @@ -683,34 +680,39 @@ class ReceiveModeRequest(Request): if self.path == '/{}/upload'.format(self.web.slug): self.upload_request = True - # If this is an upload request, create an upload_id (attach it to the request) - self.upload_id = self.web.upload_count - self.web.upload_count += 1 + if self.upload_request: + # A dictionary that maps filenames to the bytes uploaded so far + self.progress = {} - # Tell the GUI - self.web.add_request(Web.REQUEST_STARTED, self.path, { - 'id': self.upload_id - }) + # Create an upload_id, attach it to the request + self.upload_id = self.web.upload_count + self.web.upload_count += 1 + + # Tell the GUI + self.web.add_request(Web.REQUEST_STARTED, self.path, { + 'id': self.upload_id + }) def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): """ This gets called for each file that gets uploaded, and returns an file-like writable stream. """ - # Tell the GUI about the new file upload - self.web.add_request(Web.REQUEST_UPLOAD_NEW_FILE_STARTED, self.path, { - 'id': self.upload_id, - 'filename': filename, - 'total_bytes': total_content_length - }) + if self.upload_request: + # Tell the GUI about the new file upload + self.web.add_request(Web.REQUEST_UPLOAD_NEW_FILE_STARTED, self.path, { + 'id': self.upload_id, + 'filename': filename, + 'total_bytes': total_content_length + }) - self.progress[filename] = { - 'total_bytes': total_content_length, - 'uploaded_bytes': 0 - } + self.progress[filename] = { + 'total_bytes': total_content_length, + 'uploaded_bytes': 0 + } - if len(self.progress) > 0: - print('') + if len(self.progress) > 0: + print('') return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) @@ -719,20 +721,22 @@ class ReceiveModeRequest(Request): When closing the request, print a newline if this was a file upload. """ super(ReceiveModeRequest, self).close() - if len(self.progress) > 0: - print('') + if self.upload_request: + if len(self.progress) > 0: + print('') def onionshare_update_func(self, filename, length): """ Keep track of the bytes uploaded so far for all files. """ - self.progress[filename]['uploaded_bytes'] += length - uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) - total = self.web.common.human_readable_filesize(self.progress[filename]['total_bytes']) - print('{}/{} - {} '.format(uploaded, total, filename), end='\r') - - # Update the GUI on the upload progress - self.web.add_request(Web.REQUEST_PROGRESS, self.path, { - 'id': self.upload_id, - 'progress': self.progress - }) + if self.upload_request: + self.progress[filename]['uploaded_bytes'] += length + uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) + total = self.web.common.human_readable_filesize(self.progress[filename]['total_bytes']) + print('{}/{} - {} '.format(uploaded, total, filename), end='\r') + + # Update the GUI on the upload progress + self.web.add_request(Web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'progress': self.progress + }) -- cgit v1.2.3-54-g00ecf From a787a5af1ec764676b1fc1bdc0c1e7b6e173efef Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 8 May 2018 14:28:02 -0700 Subject: Start building File/Upload/Uploads GUI --- onionshare_gui/receive_mode/uploads.py | 88 +++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index a36f2364..702912c3 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -23,6 +23,67 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +class File(QtWidgets.QWidget): + def __init__(self, common, filename): + super(File, self).__init__() + self.common = common + self.filename = filename + + self.started = datetime.now() + + # Filename label + self.label = QtWidgets.QLabel(self.filename) + + # 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']) + + # Folder button + self.folder_button = QtWidgets.QPushButton("open folder") + self.folder_button.hide() + + # Layouts + info_layout = QtWidgets.QVBoxLayout() + info_layout.addWidget(self.label) + info_layout.addWidget(self.progress_bar) + + # The horizontal layout has info to the left, folder button to the right + layout = QtWidgets.QHBoxLayout() + layout.addLayout(info_layout) + layout.addWidget(self.folder_button) + self.setLayout(layout) + + def update(self, total_bytes, uploaded_bytes): + print('total_bytes: {}, uploaded_bytes: {}'.format(total_bytes, uploaded_bytes)) + if total_bytes == uploaded_bytes: + # Hide the progress bar, show the folder button + self.progress_bar.hide() + self.folder_button.show() + + else: + # Update the progress bar + self.progress_bar.setMaximum(total_bytes) + self.progress_bar.setValue(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(uploaded_bytes)) + else: + estimated_time_remaining = self.common.estimated_time_remaining( + uploaded_bytes, + total_bytes, + started.timestamp()) + pb_fmt = strings._('gui_download_upload_progress_eta').format( + self.common.human_readable_filesize(uploaded_bytes), + estimated_time_remaining) + + class Upload(QtWidgets.QGroupBox): def __init__(self, common, upload_id): super(Upload, self).__init__() @@ -35,15 +96,26 @@ class Upload(QtWidgets.QGroupBox): # Set the title of the title of the group box based on the start time self.setTitle(strings._('gui_upload_in_progress', True).format(self.started.strftime("%b %m, %I:%M%p"))) - # Start at 0 - self.update({}) + # The layout contains file widgets + self.layout = QtWidgets.QVBoxLayout() + self.setLayout(self.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, make sure all the file progress bars exist, and update their progress """ - pass + for filename in progress: + # Add a new file if needed + if filename not in self.files: + self.files[filename] = File(self.common, filename) + self.layout.addWidget(self.files[filename]) + + # Update the file + self.files[filename].update(progress[filename]['total_bytes'], progress[filename]['uploaded_bytes']) class Uploads(QtWidgets.QScrollArea): @@ -54,6 +126,7 @@ class Uploads(QtWidgets.QScrollArea): def __init__(self, common): super(Uploads, self).__init__() self.common = common + self.common.log('Uploads', '__init__') self.uploads = {} @@ -85,6 +158,7 @@ class Uploads(QtWidgets.QScrollArea): """ Add a new upload. """ + self.common.log('Uploads', 'add', 'upload_id: {}'.format(upload_id)) # Hide the no_uploads_label self.no_uploads_label.hide() @@ -96,23 +170,25 @@ class Uploads(QtWidgets.QScrollArea): # Scroll to the bottom self.vbar.setValue(self.vbar.maximum()) - def update(self, upload_id, uploaded_bytes): + def update(self, upload_id, progress): """ Update the progress of an upload progress bar. """ - pass - #self.uploads[upload_id].update(uploaded_bytes) + self.uploads[upload_id].update(progress) + self.adjustSize() 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(): self.uploads_layout.removeWidget(upload) self.uploads = {} -- cgit v1.2.3-54-g00ecf From c23ab77a589a7b0b3f0f812e633a9537367ec331 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 20:51:01 -0700 Subject: Move downloads dir validation into Common --- onionshare/__init__.py | 22 ++++++++++++---------- onionshare/common.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 86f03b84..52226b48 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -21,7 +21,7 @@ along with this program. If not, see . import os, sys, time, argparse, threading from . import strings -from .common import Common +from .common import Common, DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable from .web import Web from .onion import * from .onionshare import OnionShare @@ -92,17 +92,19 @@ def main(cwd=None): # In receive mode, validate downloads dir if receive: valid = True - if not os.path.isdir(common.settings.get('downloads_dir')): - try: - os.mkdir(common.settings.get('downloads_dir'), 0o700) - except: - print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir'))) - valid = False - if valid and not os.access(common.settings.get('downloads_dir'), os.W_OK): + try: + common.validate_downloads_dir() + + except DownloadsDirErrorCannotCreate: + print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir'))) + valid = False + + except DownloadsDirErrorNotWritable: print(strings._('error_downloads_dir_not_writable').format(common.settings.get('downloads_dir'))) valid = False - if not valid: - sys.exit() + + if not valid: + sys.exit() # Create the Web object web = Web(common, False, receive) diff --git a/onionshare/common.py b/onionshare/common.py index 628064df..ad2f4574 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -31,6 +31,21 @@ import time from .settings import Settings + +class DownloadsDirErrorCannotCreate(Exception): + """ + Error creating the downloads dir (~/OnionShare by default). + """ + pass + + +class DownloadsDirErrorNotWritable(Exception): + """ + Downloads dir is not writable. + """ + pass + + class Common(object): """ The Common object is shared amongst all parts of OnionShare. @@ -321,6 +336,19 @@ class Common(object): }""" } + def validate_downloads_dir(self): + """ + Validate that downloads_dir exists, and create it if it doesn't + """ + if not os.path.isdir(self.settings.get('downloads_dir')): + try: + os.mkdir(self.settings.get('downloads_dir'), 0o700) + except: + raise DownloadsDirErrorCannotCreate + + if not os.access(self.settings.get('downloads_dir'), os.W_OK): + raise DownloadsDirErrorNotWritable + @staticmethod def random_string(num_bytes, output_len=None): """ -- cgit v1.2.3-54-g00ecf From db7d5a65522a61df980347e5a9f4d3bbf6fdc4a9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 21:11:57 -0700 Subject: Move downloads_dir validation into the /upload request in Web, and display an error in both CLI and GUI --- onionshare/__init__.py | 17 ----------------- onionshare/web.py | 19 +++++++++++++++++++ onionshare_gui/onionshare_gui.py | 6 ++++++ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 52226b48..1cebc4e3 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -89,23 +89,6 @@ def main(cwd=None): # Debug mode? common.debug = debug - # In receive mode, validate downloads dir - if receive: - valid = True - try: - common.validate_downloads_dir() - - except DownloadsDirErrorCannotCreate: - print(strings._('error_cannot_create_downloads_dir').format(common.settings.get('downloads_dir'))) - valid = False - - except DownloadsDirErrorNotWritable: - print(strings._('error_downloads_dir_not_writable').format(common.settings.get('downloads_dir'))) - valid = False - - if not valid: - sys.exit() - # Create the Web object web = Web(common, False, receive) diff --git a/onionshare/web.py b/onionshare/web.py index 282ddd81..3d1279ce 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -39,6 +39,7 @@ from flask import ( from werkzeug.utils import secure_filename from . import strings +from .common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable class Web(object): """ @@ -53,6 +54,8 @@ class Web(object): REQUEST_CLOSE_SERVER = 6 REQUEST_UPLOAD_NEW_FILE_STARTED = 7 REQUEST_UPLOAD_FILE_RENAMED = 8 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -306,6 +309,22 @@ class Web(object): """ Upload files. """ + # Make sure downloads_dir exists + valid = True + try: + self.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user') + return redirect('/{}'.format(slug_candidate)) + files = request.files.getlist('file[]') filenames = [] for f in files: diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 893d2dae..10fa62b6 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -393,6 +393,12 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) + if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: + Alert(self.common, strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + + if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE: + Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + elif event["path"] != '/favicon.ico': self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) -- cgit v1.2.3-54-g00ecf From caf87b8d96bd1702e91021720c46d109e74bfdf3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 21:20:51 -0700 Subject: Fix bug where ReceiveModeRequest was not recognizing an upload request if the POST included a slug when receive_public_mode == True --- onionshare/web.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 3d1279ce..a3144eb2 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -692,12 +692,12 @@ class ReceiveModeRequest(Request): # Is this a valid upload request? self.upload_request = False if self.method == 'POST': - if self.web.common.settings.get('receive_public_mode'): - if self.path == '/upload': - self.upload_request = True + if self.path == '/{}/upload'.format(self.web.slug): + self.upload_request = True else: - if self.path == '/{}/upload'.format(self.web.slug): - self.upload_request = True + if self.web.common.settings.get('receive_public_mode'): + if self.path == '/upload': + self.upload_request = True if self.upload_request: # A dictionary that maps filenames to the bytes uploaded so far -- cgit v1.2.3-54-g00ecf From ee9c0d0abb326bc8e59e35212399e4adf135e27b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 22:36:08 -0700 Subject: Refactor uploads GUI so that each upload POST has one progess bar, and a list of files, with partial styling --- onionshare/common.py | 13 ++++ onionshare/web.py | 23 ++++-- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/receive_mode/uploads.py | 130 +++++++++++++++++++------------- share/images/open_folder.png | Bin 0 -> 221 bytes share/locale/en.json | 2 +- 6 files changed, 110 insertions(+), 60 deletions(-) create mode 100644 share/images/open_folder.png diff --git a/onionshare/common.py b/onionshare/common.py index ad2f4574..646cbba2 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -322,6 +322,19 @@ class Common(object): font-size: 11px; }""", + # Recieve mode and child widget styles + 'receive_file': """ + QWidget { + background-color: #ffffff; + } + """, + + 'receive_file_size': """ + QLabel { + color: #666666; + font-size: 11px; + }""", + # Settings dialog 'settings_version': """ QLabel { diff --git a/onionshare/web.py b/onionshare/web.py index a3144eb2..4f521bf5 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -707,9 +707,16 @@ class ReceiveModeRequest(Request): self.upload_id = self.web.upload_count self.web.upload_count += 1 + # Figure out the content length + try: + self.content_length = int(self.headers['Content-Length']) + except: + self.content_length = 0 + # Tell the GUI self.web.add_request(Web.REQUEST_STARTED, self.path, { - 'id': self.upload_id + 'id': self.upload_id, + 'content_length': self.content_length }) def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): @@ -726,8 +733,8 @@ class ReceiveModeRequest(Request): }) self.progress[filename] = { - 'total_bytes': total_content_length, - 'uploaded_bytes': 0 + 'uploaded_bytes': 0, + 'complete': False } if len(self.progress) > 0: @@ -749,10 +756,14 @@ class ReceiveModeRequest(Request): Keep track of the bytes uploaded so far for all files. """ if self.upload_request: - self.progress[filename]['uploaded_bytes'] += length + # The final write, when upload is complete, length will be 0 + if length == 0: + self.progress[filename]['complete'] = True + else: + self.progress[filename]['uploaded_bytes'] += length + uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) - total = self.web.common.human_readable_filesize(self.progress[filename]['total_bytes']) - print('{}/{} - {} '.format(uploaded, total, filename), end='\r') + print('{} - {} '.format(uploaded, filename), end='\r') # Update the GUI on the upload progress self.web.add_request(Web.REQUEST_PROGRESS, self.path, { diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 000850d2..0d2ef61b 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -135,7 +135,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_STARTED event. """ - self.uploads.add(event["data"]["id"]) + self.uploads.add(event["data"]["id"], event["data"]["content_length"]) self.uploads_in_progress += 1 self.update_uploads_in_progress() diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 702912c3..2a999f86 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -32,7 +32,48 @@ class File(QtWidgets.QWidget): self.started = datetime.now() # Filename label - self.label = QtWidgets.QLabel(self.filename) + self.filename_label = QtWidgets.QLabel(self.filename) + + # 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.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() + + +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', True).format(self.started.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -43,79 +84,64 @@ class File(QtWidgets.QWidget): self.progress_bar.setValue(0) self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) - # Folder button - self.folder_button = QtWidgets.QPushButton("open folder") - self.folder_button.hide() - - # Layouts - info_layout = QtWidgets.QVBoxLayout() - info_layout.addWidget(self.label) - info_layout.addWidget(self.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) - # The horizontal layout has info to the left, folder button to the right - layout = QtWidgets.QHBoxLayout() - layout.addLayout(info_layout) - layout.addWidget(self.folder_button) + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.progress_bar) + layout.addWidget(files_widget) + layout.addStretch() self.setLayout(layout) - def update(self, total_bytes, uploaded_bytes): - print('total_bytes: {}, uploaded_bytes: {}'.format(total_bytes, uploaded_bytes)) - if total_bytes == uploaded_bytes: + # 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'] + + if total_uploaded_bytes == self.content_length: # Hide the progress bar, show the folder button self.progress_bar.hide() self.folder_button.show() else: # Update the progress bar - self.progress_bar.setMaximum(total_bytes) - self.progress_bar.setValue(uploaded_bytes) + 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(uploaded_bytes)) + self.common.human_readable_filesize(total_uploaded_bytes)) else: estimated_time_remaining = self.common.estimated_time_remaining( - uploaded_bytes, - total_bytes, + total_uploaded_bytes, + self.content_length, started.timestamp()) pb_fmt = strings._('gui_download_upload_progress_eta').format( - self.common.human_readable_filesize(uploaded_bytes), + self.common.human_readable_filesize(total_uploaded_bytes), estimated_time_remaining) - -class Upload(QtWidgets.QGroupBox): - def __init__(self, common, upload_id): - super(Upload, self).__init__() - self.common = common - - self.upload_id = upload_id - self.started = datetime.now() - self.uploaded_bytes = 0 - - # Set the title of the title of the group box based on the start time - self.setTitle(strings._('gui_upload_in_progress', True).format(self.started.strftime("%b %m, %I:%M%p"))) - - # The layout contains file widgets - self.layout = QtWidgets.QVBoxLayout() - self.setLayout(self.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, make sure all the file progress bars exist, - and update their progress - """ for filename in progress: # Add a new file if needed if filename not in self.files: self.files[filename] = File(self.common, filename) - self.layout.addWidget(self.files[filename]) + self.files_layout.addWidget(self.files[filename]) # Update the file - self.files[filename].update(progress[filename]['total_bytes'], progress[filename]['uploaded_bytes']) + self.files[filename].update(progress[filename]['uploaded_bytes'], progress[filename]['complete']) class Uploads(QtWidgets.QScrollArea): @@ -154,16 +180,16 @@ class Uploads(QtWidgets.QScrollArea): widget.setLayout(layout) self.setWidget(widget) - def add(self, upload_id): + def add(self, upload_id, content_length): """ Add a new upload. """ - self.common.log('Uploads', 'add', 'upload_id: {}'.format(upload_id)) + self.common.log('Uploads', 'add', 'upload_id: {}, content_length: {}'.format(upload_id, content_length)) # Hide the no_uploads_label self.no_uploads_label.hide() # Add it to the list - upload = Upload(self.common, upload_id) + upload = Upload(self.common, upload_id, content_length) self.uploads[upload_id] = upload self.uploads_layout.addWidget(upload) diff --git a/share/images/open_folder.png b/share/images/open_folder.png new file mode 100644 index 00000000..0a734c41 Binary files /dev/null and b/share/images/open_folder.png differ diff --git a/share/locale/en.json b/share/locale/en.json index 4b8c2c04..9874d710 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -191,6 +191,6 @@ "gui_uploads": "Upload History", "gui_uploads_window_tooltip": "Show/hide uploads", "gui_no_uploads": "No uploads yet.", - "gui_upload_in_progress": "Upload in progress, started {}", + "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished": "Uploaded {} to {}" } -- cgit v1.2.3-54-g00ecf From 7a571764ef2b8776dc36d32cfa7266fcf382d334 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 22:58:55 -0700 Subject: Allow file uploads to finish, and improve uploads styling --- onionshare/web.py | 10 ++++- onionshare_gui/mode.py | 6 +++ onionshare_gui/onionshare_gui.py | 3 ++ onionshare_gui/receive_mode/__init__.py | 6 +++ onionshare_gui/receive_mode/uploads.py | 69 ++++++++++++++++++++++----------- share/locale/en.json | 3 +- 6 files changed, 71 insertions(+), 26 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 4f521bf5..d3e9f3c7 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -54,8 +54,9 @@ class Web(object): REQUEST_CLOSE_SERVER = 6 REQUEST_UPLOAD_NEW_FILE_STARTED = 7 REQUEST_UPLOAD_FILE_RENAMED = 8 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 - REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 + REQUEST_UPLOAD_FINISHED = 9 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 10 + REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 11 def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -748,6 +749,11 @@ class ReceiveModeRequest(Request): """ super(ReceiveModeRequest, self).close() if self.upload_request: + # Inform the GUI that the upload has finished + self.web.add_request(Web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': self.upload_id + }) + if len(self.progress) > 0: print('') diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index edcedca3..c81d9895 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -335,3 +335,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_FILE_RENAMED event. """ pass + + def handle_request_upload_finished(self, event): + """ + Handle REQUEST_UPLOAD_FINISHED event. + """ + pass diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 10fa62b6..c2bfa749 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -393,6 +393,9 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) + elif event["type"] == Web.REQUEST_UPLOAD_FINISHED: + mode.handle_request_upload_finished(event) + if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: Alert(self.common, strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 0d2ef61b..65d3905a 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -168,6 +168,12 @@ class ReceiveMode(Mode): """ pass + def handle_request_upload_finished(self, event): + """ + Handle REQUEST_UPLOAD_FINISHED event. + """ + self.uploads.finished(event["data"]["id"]) + def reset_info_counters(self): """ Set the info counters back to zero. diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 2a999f86..38d127c3 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -111,28 +111,22 @@ class Upload(QtWidgets.QWidget): for filename in progress: total_uploaded_bytes += progress[filename]['uploaded_bytes'] - if total_uploaded_bytes == self.content_length: - # Hide the progress bar, show the folder button - self.progress_bar.hide() - self.folder_button.show() - + # 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: - # 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, - started.timestamp()) - pb_fmt = strings._('gui_download_upload_progress_eta').format( - self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) + 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) for filename in progress: # Add a new file if needed @@ -143,6 +137,29 @@ class Upload(QtWidgets.QWidget): # Update the file self.files[filename].update(progress[filename]['uploaded_bytes'], progress[filename]['complete']) + 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', True).format( + self.started.strftime("%b %d, %I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).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): """ @@ -198,10 +215,16 @@ class Uploads(QtWidgets.QScrollArea): def update(self, upload_id, progress): """ - Update the progress of an upload progress bar. + Update the progress of an upload. """ self.uploads[upload_id].update(progress) - self.adjustSize() + #self.adjustSize() + + def finished(self, upload_id): + """ + An upload has finished. + """ + self.uploads[upload_id].finished() def cancel(self, upload_id): """ diff --git a/share/locale/en.json b/share/locale/en.json index 9874d710..bd0f939d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -192,5 +192,6 @@ "gui_uploads_window_tooltip": "Show/hide uploads", "gui_no_uploads": "No uploads yet.", "gui_upload_in_progress": "Upload Started {}", - "gui_upload_finished": "Uploaded {} to {}" + "gui_upload_finished_range": "Uploaded {} to {}", + "gui_upload_finished": "Uploaded {}" } -- cgit v1.2.3-54-g00ecf From 8939d279e383cf2e344bf24b86b9c3dcf2be9007 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 11:04:45 -0700 Subject: Only show other_page_loaded message on actual 404s --- onionshare_gui/onionshare_gui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index c2bfa749..80e5fba9 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -402,8 +402,9 @@ class OnionShareGui(QtWidgets.QMainWindow): if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE: Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - elif event["path"] != '/favicon.ico': - self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) + if event["type"] == Web.REQUEST_OTHER: + if event["path"] != '/favicon.ico': + self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(mode.web.error404_count, strings._('other_page_loaded', True), event["path"])) mode.timer_callback() -- cgit v1.2.3-54-g00ecf From 9857d9fce812f84c9a42d8cb84aed39348e05745 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 11:16:09 -0700 Subject: Make the receive.html template not use slugs if receive_public_mode is True, and fix some bugs with receive routes --- onionshare/web.py | 24 +++++++++++++++++++----- share/templates/receive.html | 4 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index d3e9f3c7..bf65ade6 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -288,9 +288,17 @@ class Web(object): def index_logic(): self.add_request(Web.REQUEST_LOAD, request.path) + if self.common.settings.get('receive_public_mode'): + upload_action = '/upload' + close_action = '/close' + else: + upload_action = '/{}/upload'.format(self.slug) + close_action = '/{}/close'.format(self.slug) + r = make_response(render_template( 'receive.html', - slug=self.slug, + upload_action=upload_action, + close_action=close_action, receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) return self.add_security_headers(r) @@ -324,7 +332,10 @@ class Web(object): valid = False if not valid: flash('Error uploading, please inform the OnionShare user') - return redirect('/{}'.format(slug_candidate)) + if self.common.settings.get('receive_public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) files = request.files.getlist('file[]') filenames = [] @@ -383,14 +394,17 @@ class Web(object): for filename in filenames: flash('Uploaded {}'.format(filename)) - return redirect('/{}'.format(slug_candidate)) + if self.common.settings.get('receive_public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): self.check_slug_candidate(slug_candidate) return upload_logic(slug_candidate) - @self.app.route("/upload") + @self.app.route("/upload", methods=['POST']) def upload_public(): if not self.common.settings.get('receive_public_mode'): return self.error404() @@ -411,7 +425,7 @@ class Web(object): self.check_slug_candidate(slug_candidate) return close_logic(slug_candidate) - @self.app.route("/upload") + @self.app.route("/close", methods=['POST']) def close_public(): if not self.common.settings.get('receive_public_mode'): return self.error404() diff --git a/share/templates/receive.html b/share/templates/receive.html index 7cc4319f..81b43616 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -17,7 +17,7 @@

    Send Files

    Select the files you want to send, then click "Send Files"...

    -
    +

    @@ -35,7 +35,7 @@
    {% if receive_allow_receiver_shutdown %} - +
    {% endif %} -- cgit v1.2.3-54-g00ecf From 18573ba49cd22a2b399fc2abe8fc7af2cdbaf116 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 12:07:15 -0700 Subject: Remove REQUEST_UPLOAD_NEW_FILE_STARTED event, because it's not actually needed --- onionshare/web.py | 16 ++++------------ onionshare_gui/mode.py | 6 ------ onionshare_gui/onionshare_gui.py | 3 --- onionshare_gui/receive_mode/__init__.py | 6 ------ 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index bf65ade6..70de16e7 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -52,11 +52,10 @@ class Web(object): REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 5 REQUEST_CLOSE_SERVER = 6 - REQUEST_UPLOAD_NEW_FILE_STARTED = 7 - REQUEST_UPLOAD_FILE_RENAMED = 8 - REQUEST_UPLOAD_FINISHED = 9 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 10 - REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 11 + REQUEST_UPLOAD_FILE_RENAMED = 7 + REQUEST_UPLOAD_FINISHED = 8 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 def __init__(self, common, gui_mode, receive_mode=False): self.common = common @@ -740,13 +739,6 @@ class ReceiveModeRequest(Request): writable stream. """ if self.upload_request: - # Tell the GUI about the new file upload - self.web.add_request(Web.REQUEST_UPLOAD_NEW_FILE_STARTED, self.path, { - 'id': self.upload_id, - 'filename': filename, - 'total_bytes': total_content_length - }) - self.progress[filename] = { 'uploaded_bytes': 0, 'complete': False diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index c81d9895..d2579d2c 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -324,12 +324,6 @@ class Mode(QtWidgets.QWidget): """ pass - def handle_request_upload_new_file_started(self, event): - """ - Handle REQUEST_UPLOAD_NEW_FILE_STARTED event. - """ - pass - def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 80e5fba9..ad5dee88 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -387,9 +387,6 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_CLOSE_SERVER: mode.handle_request_close_server(event) - elif event["type"] == Web.REQUEST_UPLOAD_NEW_FILE_STARTED: - mode.handle_request_upload_new_file_started(event) - elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 65d3905a..20ce3723 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -156,12 +156,6 @@ class ReceiveMode(Mode): self.stop_server() self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) - def handle_request_upload_new_file_started(self, event): - """ - Handle REQUEST_UPLOAD_NEW_FILE_STARTED event. - """ - pass - def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. -- cgit v1.2.3-54-g00ecf From d6ce902eb6d45fdba6b7adcc6a77b2d7f95924bc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 13:13:06 -0700 Subject: Only mark a file upload complete when it closes, which makes the open folder button appear --- onionshare/web.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 70de16e7..eac6b623 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -670,16 +670,17 @@ class ReceiveModeTemporaryFile(object): A custom TemporaryFile that tells ReceiveModeRequest every time data gets written to it, in order to track the progress of uploads. """ - def __init__(self, filename, update_func): + def __init__(self, filename, write_func, close_func): self.onionshare_filename = filename - self.onionshare_update_func = update_func + self.onionshare_write_func = write_func + self.onionshare_close_func = close_func # Create a temporary file self.f = tempfile.TemporaryFile('wb+') # Make all the file-like methods and attributes actually access the # TemporaryFile, except for write - attrs = ['close', 'closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', + attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'writelines'] @@ -688,10 +689,17 @@ class ReceiveModeTemporaryFile(object): def write(self, b): """ - Custom write method that calls out to onionshare_update_func + Custom write method that calls out to onionshare_write_func """ bytes_written = self.f.write(b) - self.onionshare_update_func(self.onionshare_filename, bytes_written) + self.onionshare_write_func(self.onionshare_filename, bytes_written) + + def close(self): + """ + Custom close method that calls out to onionshare_close_func + """ + self.f.close() + self.onionshare_close_func(self.onionshare_filename) class ReceiveModeRequest(Request): @@ -747,7 +755,7 @@ class ReceiveModeRequest(Request): if len(self.progress) > 0: print('') - return ReceiveModeTemporaryFile(filename, self.onionshare_update_func) + return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) def close(self): """ @@ -763,16 +771,12 @@ class ReceiveModeRequest(Request): if len(self.progress) > 0: print('') - def onionshare_update_func(self, filename, length): + def file_write_func(self, filename, length): """ - Keep track of the bytes uploaded so far for all files. + This function gets called when a specific file is written to. """ if self.upload_request: - # The final write, when upload is complete, length will be 0 - if length == 0: - self.progress[filename]['complete'] = True - else: - self.progress[filename]['uploaded_bytes'] += length + self.progress[filename]['uploaded_bytes'] += length uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) print('{} - {} '.format(uploaded, filename), end='\r') @@ -782,3 +786,9 @@ class ReceiveModeRequest(Request): 'id': self.upload_id, 'progress': self.progress }) + + def file_close_func(self, filename): + """ + This function gets called when a specific file is closed. + """ + self.progress[filename]['complete'] = True -- cgit v1.2.3-54-g00ecf From 451e07269f377e5bb3f2d053b18279b6d977d380 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:05:34 -0700 Subject: Fixed "RuntimeError: dictionary changed size during iteration" exception while updating upload progress --- onionshare_gui/receive_mode/uploads.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 38d127c3..48ea5909 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -128,7 +128,8 @@ class Upload(QtWidgets.QWidget): self.common.human_readable_filesize(total_uploaded_bytes), estimated_time_remaining) - for filename in progress: + # 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) -- cgit v1.2.3-54-g00ecf From b20ba6fc8647e7b49600b08ef14032783e2740bf Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:12:53 -0700 Subject: Rename uploaded files --- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/receive_mode/uploads.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 20ce3723..0bf8cbe2 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -160,7 +160,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - pass + self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"]) def handle_request_upload_finished(self, event): """ diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 48ea5909..784ecaaa 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -63,6 +63,10 @@ class File(QtWidgets.QWidget): if complete: self.folder_button.show() + def rename(self, new_filename): + self.filename = new_filename + self.filename_label.setText(self.filename) + class Upload(QtWidgets.QWidget): def __init__(self, common, upload_id, content_length): @@ -138,6 +142,10 @@ class Upload(QtWidgets.QWidget): # 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() @@ -219,7 +227,12 @@ class Uploads(QtWidgets.QScrollArea): Update the progress of an upload. """ self.uploads[upload_id].update(progress) - #self.adjustSize() + + 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): """ -- cgit v1.2.3-54-g00ecf From f5ce0690314f79ed22d9615226aab52ec71c2a1f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:40:27 -0700 Subject: Make it so the open folder button works in Linux, with nautilus --- onionshare_gui/receive_mode/uploads.py | 36 +++++++++++++++++++++++++++++++++- share/locale/en.json | 3 ++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 784ecaaa..203a9804 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -17,18 +17,23 @@ 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 . """ +import os +import subprocess 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.filename = filename + self.common.log('File', '__init__', 'filename: {}'.format(filename)) + + self.filename = filename self.started = datetime.now() # Filename label @@ -43,6 +48,7 @@ class File(QtWidgets.QWidget): 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) @@ -67,6 +73,34 @@ class File(QtWidgets.QWidget): 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): diff --git a/share/locale/en.json b/share/locale/en.json index bd0f939d..e0658a49 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -193,5 +193,6 @@ "gui_no_uploads": "No uploads yet.", "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", - "gui_upload_finished": "Uploaded {}" + "gui_upload_finished": "Uploaded {}", + "gui_open_folder_error_nautilus": "Cannot open folder the because nautilus is not available. You can find this file here: {}" } -- cgit v1.2.3-54-g00ecf From 96a680e05d8ce71bd7593a0165ac0403122b62eb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 15:20:21 -0700 Subject: Improve the CLI output for receive mode --- onionshare/web.py | 28 +++++++++++++++++++--------- share/locale/en.json | 1 + 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index eac6b623..94fc5396 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -31,6 +31,7 @@ import re import io from distutils.version import LooseVersion as Version from urllib.request import urlopen +from datetime import datetime from flask import ( Flask, Response, Request, request, render_template, abort, make_response, @@ -338,6 +339,7 @@ class Web(object): files = request.files.getlist('file[]') filenames = [] + print('') for f in files: if f.filename != '': # Automatically rename the file, if a file of the same name already exists @@ -735,12 +737,19 @@ class ReceiveModeRequest(Request): except: self.content_length = 0 + print("{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) + )) + # Tell the GUI self.web.add_request(Web.REQUEST_STARTED, self.path, { 'id': self.upload_id, 'content_length': self.content_length }) + self.previous_file = None + def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): """ This gets called for each file that gets uploaded, and returns an file-like @@ -752,14 +761,11 @@ class ReceiveModeRequest(Request): 'complete': False } - if len(self.progress) > 0: - print('') - return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) def close(self): """ - When closing the request, print a newline if this was a file upload. + Closing the request. """ super(ReceiveModeRequest, self).close() if self.upload_request: @@ -768,9 +774,6 @@ class ReceiveModeRequest(Request): 'id': self.upload_id }) - if len(self.progress) > 0: - print('') - def file_write_func(self, filename, length): """ This function gets called when a specific file is written to. @@ -778,8 +781,15 @@ class ReceiveModeRequest(Request): if self.upload_request: self.progress[filename]['uploaded_bytes'] += length - uploaded = self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']) - print('{} - {} '.format(uploaded, filename), end='\r') + if self.previous_file != filename: + if self.previous_file is not None: + print('') + self.previous_file = filename + + print('\r=> {:15s} {}'.format( + self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), + filename + ), end='') # Update the GUI on the upload progress self.web.add_request(Web.REQUEST_PROGRESS, self.path, { diff --git a/share/locale/en.json b/share/locale/en.json index e0658a49..b1d247d9 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -175,6 +175,7 @@ "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", "gui_receive_mode_warning": "Some files can hack your computer if you open them!
    Only open files from people you trust, or if you know what you're doing.", + "receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", "gui_mode_receive_button": "Receive Files", -- cgit v1.2.3-54-g00ecf From 4fd93636daf1048339be8c6e42f707e716dff45d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 15:33:13 -0700 Subject: Remove TODO comment --- onionshare_gui/receive_mode/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 0bf8cbe2..90100efa 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -147,8 +147,6 @@ class ReceiveMode(Mode): """ self.uploads.update(event["data"]["id"], event["data"]["progress"]) - # TODO: not done yet - def handle_request_close_server(self, event): """ Handle REQUEST_CLOSE_SERVER event. -- cgit v1.2.3-54-g00ecf From 7e7611bc346d899514e3f482d70f99abb8e4c4f7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 28 May 2018 15:52:43 +1000 Subject: Fix some lingering attribute/module references that had not been updated to use their new names/paths --- onionshare_gui/onionshare_gui.py | 4 ++-- onionshare_gui/settings_dialog.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index d5a0889a..527129e9 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -560,7 +560,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.primary_action.hide() self.info_widget.hide() self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'): + if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'): self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) # scroll to the bottom of the dl progress bar log pane @@ -585,7 +585,7 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == self.web.REQUEST_DOWNLOAD: self.downloads.no_downloads_label.hide() - self.downloads.add_download(event["data"]["id"], web.zip_filesize) + self.downloads.add_download(event["data"]["id"], self.web.zip_filesize) self.new_download = True self.downloads_in_progress += 1 self.update_downloads_in_progress(self.downloads_in_progress) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 94aa8342..20c7403d 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -716,7 +716,7 @@ class SettingsDialog(QtWidgets.QDialog): self.common.log('SettingsDialog', 'save_clicked', 'rebooting the Onion') self.onion.cleanup() - tor_con = TorConnectionDialog(self.qtapp, settings, self.onion) + tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion, settings) tor_con.start() self.common.log('SettingsDialog', 'save_clicked', 'Onion done rebooting, connected to Tor: {}'.format(self.onion.connected_to_tor)) -- cgit v1.2.3-54-g00ecf From 41c10b1ea29ca94b6f66c4cbc7614c3f541d7dd5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 28 May 2018 16:14:44 +1000 Subject: Fix installation of static assets on MacOS and probably Windows too --- install/pyinstaller.spec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index a4f1532a..716eec88 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -21,7 +21,9 @@ a = Analysis( ('../share/images/*', 'share/images'), ('../share/locale/*', 'share/locale'), ('../share/templates/*', 'share/templates'), - ('../share/static/*', 'share/static') + ('../share/static/css/*', 'share/static/css'), + ('../share/static/img/*', 'share/static/img'), + ('../share/static/js/*', 'share/static/js') ], hiddenimports=[], hookspath=[], -- cgit v1.2.3-54-g00ecf From a4f0b5e8f8287a9e60ca59b472a60b28e7f43776 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 13 Jul 2018 15:50:17 +1000 Subject: Remove duplicate line --- onionshare_gui/onionshare_gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index ad5dee88..4599f35b 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -147,7 +147,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.url_copied.connect(self.copy_url) self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.receive_mode.set_server_active.connect(self.set_server_active) - self.receive_mode.set_server_active.connect(self.set_server_active) self.update_mode_switcher() self.update_server_status_indicator() -- cgit v1.2.3-54-g00ecf From 89e341c8ec2bdce41444207d32b5c22d4f6a6d73 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 14 Jul 2018 16:19:16 +1000 Subject: #707 Hide/show the primary action in Receive Mode when tor connection is lost/regained --- onionshare_gui/onionshare_gui.py | 2 ++ onionshare_gui/receive_mode/__init__.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index ad5dee88..6d52ed01 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -308,6 +308,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if not self.timer.isActive(): self.timer.start(500) self.share_mode.on_reload_settings() + self.receive_mode.on_reload_settings() self.status_bar.clearMessage() # If we switched off the shutdown timeout setting, ensure the widget is hidden. @@ -351,6 +352,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.system_tray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) self.share_mode.handle_tor_broke() + self.receive_mode.handle_tor_broke() # Process events from the web object if self.mode == self.MODE_SHARE: diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 90100efa..623d3986 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -123,6 +123,7 @@ class ReceiveMode(Mode): """ Connection to Tor broke. """ + self.primary_action.hide() self.info_widget.hide() def handle_request_load(self, event): @@ -166,6 +167,13 @@ class ReceiveMode(Mode): """ self.uploads.finished(event["data"]["id"]) + def on_reload_settings(self): + """ + We should be ok to re-enable the 'Start Receive Mode' button now. + """ + self.primary_action.show() + self.info_widget.show() + def reset_info_counters(self): """ Set the info counters back to zero. -- cgit v1.2.3-54-g00ecf From 69ae29272cf834a55607d00474dce5fca943d94e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 14 Jul 2018 16:43:21 +1000 Subject: Fix minor spelling/grammar issues --- onionshare/common.py | 2 +- onionshare_gui/onionshare_gui.py | 2 +- test/test_onionshare_web.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 646cbba2..61663f23 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -322,7 +322,7 @@ class Common(object): font-size: 11px; }""", - # Recieve mode and child widget styles + # Receive mode and child widget styles 'receive_file': """ QWidget { background-color: #ffffff; diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index ad5dee88..0f6ec78b 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -168,7 +168,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() - # The servers isn't active yet + # The server isn't active yet self.set_server_active(False) # Create the timer diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 0b96359b..89f02df6 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -38,11 +38,11 @@ DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') -def web_obj(common_obj, recieve_mode, num_files=0): +def web_obj(common_obj, receive_mode, num_files=0): """ Creates a Web object, in either share mode or receive mode, ready for testing """ common_obj.load_settings() - web = Web(common_obj, False, recieve_mode) + web = Web(common_obj, False, receive_mode) web.generate_slug() web.stay_open = True web.running = True @@ -50,7 +50,7 @@ def web_obj(common_obj, recieve_mode, num_files=0): web.app.testing = True # Share mode - if not recieve_mode: + if not receive_mode: # Add files files = [] for i in range(num_files): -- cgit v1.2.3-54-g00ecf From 4092a65e0c7f145e3dc816e76931baac8863a904 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Jul 2018 11:45:14 +1000 Subject: Improve styling of flash() messages by using categories, and style the closed.html. Replace references to 'Uploading' with 'Sending' for consistency --- onionshare/web.py | 6 +++--- share/static/css/style.css | 50 ++++++++++++++++++++++++++++++++++++++++++++ share/templates/closed.html | 14 ++++++++++++- share/templates/receive.html | 8 +++---- 4 files changed, 70 insertions(+), 8 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 94fc5396..01cd8785 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -331,7 +331,7 @@ class Web(object): print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) valid = False if not valid: - flash('Error uploading, please inform the OnionShare user') + flash('Error uploading, please inform the OnionShare user', 'error') if self.common.settings.get('receive_public_mode'): return redirect('/') else: @@ -390,10 +390,10 @@ class Web(object): # Note that flash strings are on English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user if len(filenames) == 0: - flash('No files uploaded') + flash('No files uploaded', 'info') else: for filename in filenames: - flash('Uploaded {}'.format(filename)) + flash('Sent {}'.format(filename), 'info') if self.common.settings.get('receive_public_mode'): return redirect('/') diff --git a/share/static/css/style.css b/share/static/css/style.css index 29b839a7..7f5f4310 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -142,3 +142,53 @@ ul.flashes li { margin: 0; padding: 10px; } + +li.error { + list-style: none; + margin: 0; + padding: 0; + color: #ffffff; + background-color: #c90c0c; + border: 0; + border-radius: 5px; + text-align: left; +} + +li.info { + list-style: none; + margin: 0; + padding: 0; + color: #000000; + background-color: #a9e26c; + border: 0; + border-radius: 5px; + text-align: left; +} + +.closed-wrapper { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; +} + +.closed { + text-align: center; +} + +.closed img { + width: 120px; + height: 120px; +} + +.closed .closed-header { + font-size: 30px; + font-weight: normal; + color: #666666; + margin: 0 0 10px 0; +} + +.closed .closed-description { + color: #666666; + margin: 0 0 20px 0; +} diff --git a/share/templates/closed.html b/share/templates/closed.html index 167d0efc..c34e0ee4 100644 --- a/share/templates/closed.html +++ b/share/templates/closed.html @@ -3,8 +3,20 @@ OnionShare is closed + -

    Thank you for using OnionShare

    +
    + +

    OnionShare

    +
    + +
    +
    +

    +

    Thank you for using OnionShare

    +

    You may now close this window.

    +
    +
    diff --git a/share/templates/receive.html b/share/templates/receive.html index 81b43616..bc8f8e97 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -21,11 +21,11 @@

    - {% with messages = get_flashed_messages() %} + {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %}
      - {% for message in messages %} -
    • {{ message }}
    • + {% for category, message in messages %} +
    • {{ message }}
    • {% endfor %}
    {% endif %} @@ -36,7 +36,7 @@
    {% if receive_allow_receiver_shutdown %}
    - +
    {% endif %} -- cgit v1.2.3-54-g00ecf From e37dbb3efbe4974caf0a59d8ebaffda7c1d3fa62 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Jul 2018 11:53:55 +1000 Subject: Only show the 'I'm Finished Sending' button if the user actually already sent (or tried to send) anything --- share/templates/receive.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/share/templates/receive.html b/share/templates/receive.html index bc8f8e97..d8b02f73 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -35,9 +35,13 @@ {% if receive_allow_receiver_shutdown %} -
    - -
    + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + +
    + {% endif %} + {% endwith %} {% endif %} -- cgit v1.2.3-54-g00ecf From f6b031bc2ced52c536fa89ee0d74cd650f21fda2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 21 Jul 2018 15:50:37 +1000 Subject: Fix another two places where Alert was called without passing the common object --- onionshare_gui/settings_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 20c7403d..83c4eb25 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -503,7 +503,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_textbox_options.hide() # 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_amazon'): - Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ @@ -513,7 +513,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_textbox_options.hide() # 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'): - Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) + Alert(self.common, strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) def tor_bridges_use_custom_radio_toggled(self, checked): """ -- cgit v1.2.3-54-g00ecf From a830eb57fee29d16e992946b4aa490caaef2d940 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 21 Jul 2018 15:52:11 +1000 Subject: #691 remove meek_lite Amazon --- install/onionshare.nsi | 2 -- install/pyinstaller.spec | 1 - onionshare/onion.py | 6 ------ onionshare/settings.py | 1 - onionshare_gui/settings_dialog.py | 36 +----------------------------------- share/locale/en.json | 2 -- test/test_onionshare_settings.py | 2 -- 7 files changed, 1 insertion(+), 49 deletions(-) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index d037ba58..134ff8d2 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -197,7 +197,6 @@ Section "install" File "${BINPATH}\share\torrc_template" File "${BINPATH}\share\torrc_template-windows" File "${BINPATH}\share\torrc_template-obfs4" - File "${BINPATH}\share\torrc_template-meek_lite_amazon" File "${BINPATH}\share\torrc_template-meek_lite_azure" File "${BINPATH}\share\version.txt" File "${BINPATH}\share\wordlist.txt" @@ -434,7 +433,6 @@ FunctionEnd Delete "$INSTDIR\share\torrc_template" Delete "$INSTDIR\share\torrc_template-windows" Delete "$INSTDIR\share\torrc_template-obfs4" - Delete "$INSTDIR\share\torrc_template-meek_lite_amazon" Delete "$INSTDIR\share\torrc_template-meek_lite_azure" Delete "$INSTDIR\share\version.txt" Delete "$INSTDIR\share\wordlist.txt" diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index e679ec6e..b8abe5fe 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -14,7 +14,6 @@ a = Analysis( ('../share/wordlist.txt', 'share'), ('../share/torrc_template', 'share'), ('../share/torrc_template-obfs4', 'share'), - ('../share/torrc_template-meek_lite_amazon', 'share'), ('../share/torrc_template-meek_lite_azure', 'share'), ('../share/torrc_template-windows', 'share'), ('../share/images/*', 'share/images'), diff --git a/onionshare/onion.py b/onionshare/onion.py index 461af6a2..6132cc9a 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -210,11 +210,6 @@ class Onion(object): with open(self.common.get_resource_path('torrc_template-obfs4')) as o: for line in o: f.write(line) - elif self.settings.get('tor_bridges_use_meek_lite_amazon'): - f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) - with open(self.common.get_resource_path('torrc_template-meek_lite_amazon')) as o: - for line in o: - f.write(line) elif self.settings.get('tor_bridges_use_meek_lite_azure'): f.write('ClientTransportPlugin meek_lite exec {}\n'.format(self.obfs4proxy_file_path)) with open(self.common.get_resource_path('torrc_template-meek_lite_azure')) as o: @@ -281,7 +276,6 @@ class Onion(object): # If using bridges, it might take a bit longer to connect to Tor if self.settings.get('tor_bridges_use_custom_bridges') or \ self.settings.get('tor_bridges_use_obfs4') or \ - self.settings.get('tor_bridges_use_meek_lite_amazon') or \ self.settings.get('tor_bridges_use_meek_lite_azure'): connect_timeout = 150 else: diff --git a/onionshare/settings.py b/onionshare/settings.py index a8f2521f..2d056aa6 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -66,7 +66,6 @@ class Settings(object): 'autoupdate_timestamp': None, 'no_bridges': True, 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', 'save_private_key': False, diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index e9d3ed8f..07b63fe6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -168,16 +168,6 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_obfs4_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_obfs4_radio_option', True)) self.tor_bridges_use_obfs4_radio.toggled.connect(self.tor_bridges_use_obfs4_radio_toggled) - # meek_lite-amazon option radio - # if the obfs4proxy binary is missing, we can't use meek_lite-amazon 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() - if not os.path.isfile(self.obfs4proxy_file_path): - self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy', True)) - self.tor_bridges_use_meek_lite_amazon_radio.setEnabled(False) - else: - self.tor_bridges_use_meek_lite_amazon_radio = QtWidgets.QRadioButton(strings._('gui_settings_tor_bridges_meek_lite_amazon_radio_option', True)) - self.tor_bridges_use_meek_lite_amazon_radio.toggled.connect(self.tor_bridges_use_meek_lite_amazon_radio_toggled) - # meek_lite-azure option radio # 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() @@ -190,7 +180,6 @@ class SettingsDialog(QtWidgets.QDialog): # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser if self.system == 'Windows' or self.system == 'Darwin': - self.tor_bridges_use_meek_lite_amazon_radio.hide() self.tor_bridges_use_meek_lite_azure_radio.hide() # Custom bridges radio and textbox @@ -216,7 +205,6 @@ class SettingsDialog(QtWidgets.QDialog): bridges_layout = QtWidgets.QVBoxLayout() bridges_layout.addWidget(self.tor_bridges_no_bridges_radio) bridges_layout.addWidget(self.tor_bridges_use_obfs4_radio) - bridges_layout.addWidget(self.tor_bridges_use_meek_lite_amazon_radio) bridges_layout.addWidget(self.tor_bridges_use_meek_lite_azure_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_radio) bridges_layout.addWidget(self.tor_bridges_use_custom_textbox_options) @@ -450,13 +438,11 @@ class SettingsDialog(QtWidgets.QDialog): if self.old_settings.get('no_bridges'): self.tor_bridges_no_bridges_radio.setChecked(True) self.tor_bridges_use_obfs4_radio.setChecked(False) - self.tor_bridges_use_meek_lite_amazon_radio.setChecked(False) self.tor_bridges_use_meek_lite_azure_radio.setChecked(False) self.tor_bridges_use_custom_radio.setChecked(False) else: self.tor_bridges_no_bridges_radio.setChecked(False) self.tor_bridges_use_obfs4_radio.setChecked(self.old_settings.get('tor_bridges_use_obfs4')) - self.tor_bridges_use_meek_lite_amazon_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_amazon')) self.tor_bridges_use_meek_lite_azure_radio.setChecked(self.old_settings.get('tor_bridges_use_meek_lite_azure')) if self.old_settings.get('tor_bridges_use_custom_bridges'): @@ -495,16 +481,6 @@ class SettingsDialog(QtWidgets.QDialog): if checked: self.tor_bridges_use_custom_textbox_options.hide() - def tor_bridges_use_meek_lite_amazon_radio_toggled(self, checked): - """ - meek_lite-amazon bridges option was toggled. If checked, disable custom bridge options. - """ - if checked: - self.tor_bridges_use_custom_textbox_options.hide() - # 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_amazon'): - Alert(strings._('gui_settings_meek_lite_expensive_warning', True), QtWidgets.QMessageBox.Warning) - def tor_bridges_use_meek_lite_azure_radio_toggled(self, checked): """ meek_lite_azure bridges option was toggled. If checked, disable custom bridge options. @@ -700,7 +676,7 @@ class SettingsDialog(QtWidgets.QDialog): 'control_port_port', 'socks_address', 'socks_port', 'socket_file_path', 'auth_type', 'auth_password', 'no_bridges', 'tor_bridges_use_obfs4', - 'tor_bridges_use_meek_lite_amazon', 'tor_bridges_use_meek_lite_azure', + 'tor_bridges_use_meek_lite_azure', 'tor_bridges_use_custom_bridges']): reboot_onion = True @@ -810,31 +786,21 @@ class SettingsDialog(QtWidgets.QDialog): if self.tor_bridges_no_bridges_radio.isChecked(): settings.set('no_bridges', True) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', True) - settings.set('tor_bridges_use_meek_lite_amazon', False) - settings.set('tor_bridges_use_meek_lite_azure', False) - settings.set('tor_bridges_use_custom_bridges', '') - if self.tor_bridges_use_meek_lite_amazon_radio.isChecked(): - settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_amazon', True) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', True) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_amazon', False) settings.set('tor_bridges_use_meek_lite_azure', False) # Insert a 'Bridge' line at the start of each bridge. diff --git a/share/locale/en.json b/share/locale/en.json index 525dab04..4367e1e0 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -105,8 +105,6 @@ "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_amazon_radio_option": "Use built-in meek_lite (Amazon) pluggable transports", - "gui_settings_tor_bridges_meek_lite_amazon_radio_option_no_obfs4proxy": "Use built-in meek_lite (Amazon) pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", "gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!

    You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.", diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index df05ab76..bfc98f2e 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -58,7 +58,6 @@ class TestSettings: 'autoupdate_timestamp': None, 'no_bridges': True, 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', 'save_private_key': False, @@ -126,7 +125,6 @@ class TestSettings: assert settings_obj.get('autoupdate_timestamp') is None assert settings_obj.get('no_bridges') is True assert settings_obj.get('tor_bridges_use_obfs4') is False - assert settings_obj.get('tor_bridges_use_meek_lite_amazon') is False assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False assert settings_obj.get('tor_bridges_use_custom_bridges') == '' -- cgit v1.2.3-54-g00ecf From 3b45f93dbeb1d22ad15553fc4f2e0308a910f5e7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 21 Jul 2018 17:06:11 +1000 Subject: Expand 'public mode' (optional slugs) to be possible for sharing too, not just receiving, with no rate-limiting/self-destruct on invalid routes. --- onionshare/__init__.py | 4 +-- onionshare/settings.py | 4 +-- onionshare/web.py | 68 +++++++++++++++++++++++++++------------ onionshare_gui/mode.py | 7 ++-- onionshare_gui/server_status.py | 2 +- onionshare_gui/settings_dialog.py | 42 ++++++++++++++---------- share/locale/en.json | 3 +- share/templates/send.html | 4 +++ 8 files changed, 87 insertions(+), 47 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 1cebc4e3..becca93f 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -128,7 +128,7 @@ def main(cwd=None): print('') # Start OnionShare http service in new thread - t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('slug'))) + t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug'))) t.daemon = True t.start() @@ -147,7 +147,7 @@ def main(cwd=None): common.settings.save() # Build the URL - if receive and common.settings.get('receive_public_mode'): + if common.settings.get('public_mode'): url = 'http://{0:s}'.format(app.onion_host) else: url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) diff --git a/onionshare/settings.py b/onionshare/settings.py index 6d551ca0..c0e0e30c 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -70,11 +70,11 @@ class Settings(object): 'tor_bridges_use_custom_bridges': '', 'save_private_key': False, 'private_key': '', + 'public_mode': False, 'slug': '', 'hidservauth_string': '', 'downloads_dir': self.build_default_downloads_dir(), - 'receive_allow_receiver_shutdown': True, - 'receive_public_mode': False + 'receive_allow_receiver_shutdown': True } self._settings = {} self.fill_in_defaults() diff --git a/onionshare/web.py b/onionshare/web.py index 94fc5396..60f1d22d 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -143,11 +143,19 @@ class Web(object): """ @self.app.route("/") def index(slug_candidate): + self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return index_logic() + + def index_logic(slug_candidate=''): """ Render the template for the onionshare landing page. """ - self.check_slug_candidate(slug_candidate) - self.add_request(Web.REQUEST_LOAD, request.path) # Deny new downloads if "Stop After First Download" is checked and there is @@ -158,22 +166,39 @@ class Web(object): return self.add_security_headers(r) # If download is allowed to continue, serve download page - r = make_response(render_template( - 'send.html', - slug=self.slug, - file_info=self.file_info, - filename=os.path.basename(self.zip_filename), - filesize=self.zip_filesize, - filesize_human=self.common.human_readable_filesize(self.zip_filesize))) + if self.slug: + r = make_response(render_template( + 'send.html', + slug=self.slug, + file_info=self.file_info, + filename=os.path.basename(self.zip_filename), + filesize=self.zip_filesize, + filesize_human=self.common.human_readable_filesize(self.zip_filesize))) + else: + # If download is allowed to continue, serve download page + r = make_response(render_template( + 'send.html', + file_info=self.file_info, + filename=os.path.basename(self.zip_filename), + filesize=self.zip_filesize, + filesize_human=self.common.human_readable_filesize(self.zip_filesize))) return self.add_security_headers(r) @self.app.route("//download") def download(slug_candidate): + self.check_slug_candidate(slug_candidate) + return download_logic() + + @self.app.route("/download") + def download_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return download_logic() + + def download_logic(slug_candidate=''): """ Download the zip file. """ - self.check_slug_candidate(slug_candidate) - # Deny new downloads if "Stop After First Download" is checked and there is # currently a download deny_download = not self.stay_open and self.download_in_progress @@ -288,7 +313,7 @@ class Web(object): def index_logic(): self.add_request(Web.REQUEST_LOAD, request.path) - if self.common.settings.get('receive_public_mode'): + if self.common.settings.get('public_mode'): upload_action = '/upload' close_action = '/close' else: @@ -309,7 +334,7 @@ class Web(object): @self.app.route("/") def index_public(): - if not self.common.settings.get('receive_public_mode'): + if not self.common.settings.get('public_mode'): return self.error404() return index_logic() @@ -332,7 +357,7 @@ class Web(object): valid = False if not valid: flash('Error uploading, please inform the OnionShare user') - if self.common.settings.get('receive_public_mode'): + if self.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) @@ -395,7 +420,7 @@ class Web(object): for filename in filenames: flash('Uploaded {}'.format(filename)) - if self.common.settings.get('receive_public_mode'): + if self.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) @@ -407,7 +432,7 @@ class Web(object): @self.app.route("/upload", methods=['POST']) def upload_public(): - if not self.common.settings.get('receive_public_mode'): + if not self.common.settings.get('public_mode'): return self.error404() return upload_logic() @@ -428,7 +453,7 @@ class Web(object): @self.app.route("/close", methods=['POST']) def close_public(): - if not self.common.settings.get('receive_public_mode'): + if not self.common.settings.get('public_mode'): return self.error404() return close_logic() @@ -458,7 +483,7 @@ class Web(object): self.error404_count += 1 # In receive mode, with public mode enabled, skip rate limiting 404s - if not (self.receive_mode and self.common.settings.get('receive_public_mode')): + if not self.common.settings.get('public_mode'): if self.error404_count == 20: self.add_request(Web.REQUEST_RATE_LIMIT, request.path) self.force_shutdown() @@ -563,12 +588,13 @@ class Web(object): pass self.running = False - def start(self, port, stay_open=False, persistent_slug=None): + def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): """ Start the flask web server. """ self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug)) - self.generate_slug(persistent_slug) + if not public_mode: + self.generate_slug(persistent_slug) self.stay_open = stay_open @@ -719,7 +745,7 @@ class ReceiveModeRequest(Request): if self.path == '/{}/upload'.format(self.web.slug): self.upload_request = True else: - if self.web.common.settings.get('receive_public_mode'): + if self.web.common.settings.get('public_mode'): if self.path == '/upload': self.upload_request = True diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index d2579d2c..418afffd 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -144,13 +144,14 @@ class Mode(QtWidgets.QWidget): self.app.choose_port() # Start http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('slug'))) + t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('public_mode'), self.common.settings.get('slug'))) t.daemon = True t.start() # Wait for the web app slug to generate before continuing - while self.web.slug == None: - time.sleep(0.1) + if not self.common.settings.get('public_mode'): + while self.web.slug == None: + time.sleep(0.1) # Now start the onion service try: diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 1562ee10..e016e8f9 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -314,7 +314,7 @@ class ServerStatus(QtWidgets.QWidget): """ Returns the OnionShare URL. """ - if self.mode == ServerStatus.MODE_RECEIVE and self.common.settings.get('receive_public_mode'): + if self.common.settings.get('public_mode'): url = 'http://{0:s}'.format(self.app.onion_host) else: url = 'http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 94480205..057c7e53 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -52,6 +52,25 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() + # General options + + # 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.setCheckState(QtCore.Qt.Unchecked) + self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) + + # Use a slug + self.public_mode_checkbox = QtWidgets.QCheckBox() + self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + + # General options layout + general_group_layout = QtWidgets.QVBoxLayout() + general_group_layout.addWidget(self.save_private_key_checkbox) + general_group_layout.addWidget(self.public_mode_checkbox) + general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True)) + general_group.setLayout(general_group_layout) + # Sharing options # Close after first download @@ -64,16 +83,10 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - # Whether or not to save the Onion private key for reuse - self.save_private_key_checkbox = QtWidgets.QCheckBox() - self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) - # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) - sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) @@ -93,16 +106,10 @@ class SettingsDialog(QtWidgets.QDialog): 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)) - # Use a slug - self.receive_public_mode_checkbox = QtWidgets.QCheckBox() - self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) - self.receive_public_mode_checkbox.setText(strings._("gui_settings_receive_public_mode_checkbox", True)) - # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout.addLayout(downloads_layout) receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox) - receiving_group_layout.addWidget(self.receive_public_mode_checkbox) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group.setLayout(receiving_group_layout) @@ -377,6 +384,7 @@ class SettingsDialog(QtWidgets.QDialog): # Layout left_col_layout = QtWidgets.QVBoxLayout() + left_col_layout.addWidget(general_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(stealth_group) @@ -431,11 +439,11 @@ class SettingsDialog(QtWidgets.QDialog): else: self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked) - receive_public_mode = self.old_settings.get('receive_public_mode') - if receive_public_mode: - self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Checked) + public_mode = self.old_settings.get('public_mode') + if public_mode: + self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked) else: - self.receive_public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) use_stealth = self.old_settings.get('use_stealth') if use_stealth: @@ -819,7 +827,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('hidservauth_string', '') settings.set('downloads_dir', self.downloads_dir_lineedit.text()) settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) - settings.set('receive_public_mode', self.receive_public_mode_checkbox.isChecked()) + settings.set('public_mode', self.public_mode_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset if not self.stealth_checkbox.isChecked(): diff --git a/share/locale/en.json b/share/locale/en.json index b1d247d9..4624fd14 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -91,6 +91,7 @@ "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", "gui_settings_autoupdate_check_button": "Check For Upgrades", + "gui_settings_general_label": "General options", "gui_settings_sharing_label": "Sharing options", "gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", @@ -183,7 +184,7 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", - "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)", + "gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", diff --git a/share/templates/send.html b/share/templates/send.html index ba43f306..df1d3563 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -13,7 +13,11 @@
    -- cgit v1.2.3-54-g00ecf From a13e98d7bb36f0dc0ec56903914870611cb930d6 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 14:58:14 +1000 Subject: Fix tests for public_mode --- test/test_onionshare_settings.py | 2 +- test/test_onionshare_web.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 3942ab8c..0c248341 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -66,7 +66,7 @@ class TestSettings: 'hidservauth_string': '', 'downloads_dir': os.path.expanduser('~/OnionShare'), 'receive_allow_receiver_shutdown': True, - 'receive_public_mode': False + 'public_mode': False } def test_fill_in_defaults(self, settings_obj): diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 0b96359b..dbd026f4 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -168,9 +168,9 @@ class TestWeb: assert res.status_code == 302 assert web.running == True - def test_receive_mode_receive_public_mode_on(self, common_obj): + def test_public_mode_on(self, common_obj): web = web_obj(common_obj, True) - common_obj.settings.set('receive_public_mode', True) + common_obj.settings.set('public_mode', True) with web.app.test_client() as c: # Upload page should be accessible from both / and /[slug] @@ -182,9 +182,9 @@ class TestWeb: data2 = res.get_data() assert res.status_code == 200 - def test_receive_mode_receive_public_mode_off(self, common_obj): + def test_public_mode_off(self, common_obj): web = web_obj(common_obj, True) - common_obj.settings.set('receive_public_mode', False) + common_obj.settings.set('public_mode', False) with web.app.test_client() as c: # / should be a 404 -- cgit v1.2.3-54-g00ecf From d2934e74ecca5707726b8c7c38522e30afff3b13 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 15:50:37 +1000 Subject: #692 move appstream file to /usr/share/metainfo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b502e7ae..8806fd12 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ license = 'GPL v3' keywords = 'onion, share, onionshare, tor, anonymous, web server' data_files=[ (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']), - (os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']), + (os.path.join(sys.prefix, 'share/metainfo'), ['install/onionshare.appdata.xml']), (os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']), (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')), (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')), -- cgit v1.2.3-54-g00ecf From e4d1f8b8f67a138d6b250ed178576eac892984eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 16:20:19 +1000 Subject: #681 remove obsolete strings --- share/locale/cs.json | 30 -------------------------- share/locale/da.json | 39 +--------------------------------- share/locale/de.json | 17 --------------- share/locale/en.json | 60 +--------------------------------------------------- share/locale/eo.json | 30 -------------------------- share/locale/es.json | 19 +---------------- share/locale/fi.json | 27 +---------------------- share/locale/fr.json | 22 ------------------- share/locale/it.json | 27 +---------------------- share/locale/nl.json | 38 +-------------------------------- share/locale/no.json | 4 ---- share/locale/pt.json | 4 ---- share/locale/ru.json | 4 ---- share/locale/tr.json | 27 +---------------------- 14 files changed, 7 insertions(+), 341 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index aaa80d1b..9e151dd6 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -1,58 +1,31 @@ { "config_onion_service": "Nastavuji onion service na portu {0:d}.", "preparing_files": "Připravuji soubory ke sdílení.", - "wait_for_hs": "Čekám na HS až bude připravena:", - "wait_for_hs_trying": "Zkouším...", - "wait_for_hs_nope": "Ještě nepřipraven.", - "wait_for_hs_yup": "Připraven!", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", - "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", "large_filesize": "Varování: Posílání velkých souborů může trvat hodiny", - "error_tails_invalid_port": "Nesprávná hodnota, port musí být celé číslo", - "error_tails_unknown_root": "Neznámá chyba s Tails root procesem", "help_local_only": "Nepoužívat Tor: jen pro vývoj", "help_stay_open": "Nechat běžet onion service po skončení stahování", - "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Zaznamenat chyby na disk", "help_filename": "Seznam souborů a složek ke sdílení", - "gui_drag_and_drop": "Táhni a pusť\nsoubory sem", - "gui_add": "Přidat", - "gui_delete": "Smazat", - "gui_choose_items": "Vybrat", "gui_share_start_server": "Spustit sdílení", "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", - "gui_downloads": "Stahování:", - "gui_canceled": "Zrušeno", - "gui_copied_url": "URL zkopírováno do schránky", - "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_starting_server1": "Spouštím Tor onion service...", - "gui_starting_server2": "Zpracovávám soubory...", "gui_please_wait": "Prosím čekejte...", - "error_hs_dir_cannot_create": "Nejde vytvořit složka onion service {0:s}", - "error_hs_dir_not_writable": "nejde zapisovat do složky onion service {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", "gui_quit_warning_dont_quit": "Zůstat", "error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.", - "zip_progress_bar_format": "Zpracovávám soubory: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.", - "gui_menu_file_menu": "&File", - "gui_menu_settings_action": "&Settings", - "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", @@ -63,10 +36,7 @@ "gui_settings_authenticate_label": "Autentizační možnosti Toru", "gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace", "gui_settings_authenticate_password_option": "Heslo", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Heslo", - "gui_settings_cookie_label": "Cesta ke cookie", - "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Uložit", "gui_settings_button_cancel": "Zrušit", "settings_saved": "Nastavení uloženo do {}", diff --git a/share/locale/da.json b/share/locale/da.json index d36f7035..ded2d61c 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,66 +1,35 @@ { "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", "preparing_files": "Forbereder filer som skal deles.", - "wait_for_hs": "Venter på at HS bliver klar:", - "wait_for_hs_trying": "Prøver...", - "wait_for_hs_nope": "Endnu ikke klar.", - "wait_for_hs_yup": "Klar!", "give_this_url": "Giv denne URL til personen du sender filen til:", "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", "not_a_file": "{0:s} er ikke en gyldig fil.", "not_a_readable_file": "{0:s} er ikke en læsbar fil.", "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", - "download_page_loaded": "Downloadside indlæst", "other_page_loaded": "URL indlæst", "close_on_timeout": "Lukker automatisk da timeout er nået", "closing_automatically": "Lukker automatisk da download er færdig", - "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop", "large_filesize": "Advarsel: Det kan tage timer at sende store filer", - "error_tails_invalid_port": "Ugyldig værdi, port skal være et heltal", - "error_tails_unknown_root": "Ukendt fejl med Tails-rodproces", "systray_menu_exit": "Afslut", - "systray_download_started_title": "OnionShare-download startet", - "systray_download_started_message": "En bruger startede download af dine filer", - "systray_download_completed_title": "OnionShare-download færdig", - "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", - "systray_download_canceled_title": "OnionShare-download annulleret", - "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Undlad at bruge tor: kun til udvikling", "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder", - "help_transparent_torification": "Mit system er gennemsigtigt torifiseret", "help_stealth": "Opret usynlig onion-tjeneste (avanceret)", "help_debug": "Log programfejl til stdout, og log webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", - "gui_drag_and_drop": "Træk og slip\nfiler her", - "gui_add": "Tilføj", - "gui_delete": "Slet", - "gui_choose_items": "Vælg", "gui_share_start_server": "Start deling", "gui_share_stop_server": "Stop deling", "gui_copy_url": "Kopiér URL", "gui_copy_hidservauth": "Kopiér HidServAuth", - "gui_downloads": "Downloads:", - "gui_canceled": "Annulleret", - "gui_copied_url": "Kopierede URL til udklipsholder", - "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", - "gui_starting_server1": "Starter Tor onion-tjeneste...", - "gui_starting_server2": "Databehandler filer...", "gui_please_wait": "Vent venligst...", - "error_hs_dir_cannot_create": "Kan ikke oprette onion-tjenestens mappe {0:s}", - "error_hs_dir_not_writable": "onion-tjenestens mappe {0:s} er skrivebeskyttet", "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse", - "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", - "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Afslut ikke", "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.", - "zip_progress_bar_format": "Databehandler filer: %p%", "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", @@ -87,9 +56,7 @@ "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", "gui_settings_authenticate_password_option": "Adgangskode", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Adgangskode", - "gui_settings_cookie_label": "Cookiesti", "gui_settings_tor_bridges": "Understøttelse af Tor-bro", "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter", @@ -100,7 +67,6 @@ "gui_settings_button_save": "Gem", "gui_settings_button_cancel": "Annuller", "gui_settings_button_help": "Hjælp", - "gui_settings_shutdown_timeout_choice": "Sæt timer til automatisk stop?", "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", @@ -112,7 +78,6 @@ "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.", "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", - "settings_error_bundled_tor_canceled": "Tor-processen lukkede inden den blev færdig med at oprette forbindelse.", "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}", "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", @@ -129,7 +94,5 @@ "gui_tor_connection_lost": "Afbryder forbindelsen fra Tor.", "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", - "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)", - "persistent_url_in_use": "Denne deling bruger en vedvarende URL" + "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)" } diff --git a/share/locale/de.json b/share/locale/de.json index 8e87b89b..b5925b8e 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,34 +1,17 @@ { - "connecting_ctrlport": "Verbinde zum Tor-Kontrollport um den versteckten Dienst auf Port {0:d} laufen zu lassen.", - "cant_connect_ctrlport": "Konnte keine Verbindung zum Tor-Kontrollport auf Port {0:s} aufbauen. Läuft Tor?", - "cant_connect_socksport": "Konnte keine Verbindung zum Tor SOCKS5 Server auf Port {0:s} herstellen. OnionShare setzt voraus dass Tor Browser im Hintergrund läuft. Wenn du noch ihn noch noch nicht hast kannst du ihn unter https://www.torproject.org/ herunterladen.", "preparing_files": "Dateien werden vorbereitet.", - "wait_for_hs": "Warte auf HS:", - "wait_for_hs_trying": "Verbindungsversuch...", - "wait_for_hs_nope": "Noch nicht bereit.", - "wait_for_hs_yup": "Bereit!", "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", - "download_page_loaded": "Seite geladen", "other_page_loaded": "URL geladen", "closing_automatically": "Halte automatisch an, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", - "error_tails_invalid_port": "Ungültiger Wert, Port muss eine ganze Zahl sein", - "error_tails_unknown_root": "Unbekannter Fehler mit Tails root Prozess", - "help_tails_port": "Nur für Tails: Port um den Firewall zu öffnen, starte onion service", "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Fehler auf Festplatte schreiben", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", - "gui_drag_and_drop": "Drag & drop\nDateien hier", - "gui_add": "Hinzufügen", - "gui_delete": "Löschen", - "gui_choose_items": "Auswählen", "gui_share_start_server": "Server starten", "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", - "gui_downloads": "Downloads:", - "gui_copied_url": "URL wurde in die Zwischenablage kopiert", "gui_please_wait": "Bitte warten..." } diff --git a/share/locale/en.json b/share/locale/en.json index b1d247d9..cbb444ec 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -1,10 +1,6 @@ { "config_onion_service": "Configuring onion service on port {0:d}.", "preparing_files": "Preparing files to share.", - "wait_for_hs": "Waiting for HS to be ready:", - "wait_for_hs_trying": "Trying…", - "wait_for_hs_nope": "Not ready yet.", - "wait_for_hs_yup": "Ready!", "give_this_url": "Give this address to the person you're sending the file to:", "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", "give_this_url_receive": "Give this address to the people sending you files:", @@ -17,21 +13,8 @@ "other_page_loaded": "Address loaded", "close_on_timeout": "Stopped because timer expired", "closing_automatically": "Stopped because download finished", - "timeout_download_still_running": "Waiting for download to complete", "large_filesize": "Warning: Sending large files could take hours", - "error_tails_invalid_port": "Invalid value, port must be a regular number", - "error_tails_unknown_root": "Unknown error with Tails root process", "systray_menu_exit": "Quit", - "systray_download_started_title": "OnionShare Download Started", - "systray_download_started_message": "A user started downloading your files", - "systray_download_completed_title": "OnionShare Download Finished", - "systray_download_completed_message": "The user finished downloading your files", - "systray_download_canceled_title": "OnionShare Download Canceled", - "systray_download_canceled_message": "The user canceled the download", - "systray_upload_started_title": "OnionShare Upload Started", - "systray_upload_started_message": "A user started uploading files to your computer", - "systray_upload_completed_title": "OnionShare Upload Finished", - "systray_upload_completed_message": "The user finished uploading files to your computer", "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", @@ -40,37 +23,18 @@ "help_debug": "Log application errors to stdout, and log web errors to disk", "help_filename": "List of files or folders to share", "help_config": "Path to a custom JSON config file (optional)", - "gui_drag_and_drop": "Drag and drop files and folders\nto start sharing", - "gui_add": "Add", - "gui_delete": "Delete", - "gui_choose_items": "Choose", "gui_share_start_server": "Start Sharing", "gui_share_stop_server": "Stop Sharing", - "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", "gui_share_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", "gui_receive_start_server": "Start Receive Mode", "gui_receive_stop_server": "Stop Receive Mode", - "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Receive mode will expire automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "Download History", - "gui_downloads_window_tooltip": "Show/hide downloads", - "gui_no_downloads": "No downloads yet.", - "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare address", - "gui_copied_url": "The OnionShare address has been copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", - "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", - "gui_starting_server1": "Starting Tor onion service…", - "gui_starting_server2": "Compressing files…", "gui_please_wait": "Starting… Click to cancel", - "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", - "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Transfer in Progress", "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", @@ -78,7 +42,6 @@ "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", - "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", @@ -105,9 +68,7 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Password", - "gui_settings_cookie_label": "Cookie path", "gui_settings_tor_bridges": "Tor Bridge support", "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", @@ -135,7 +96,6 @@ "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", - "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", @@ -152,7 +112,6 @@ "gui_tor_connection_lost": "Disconnected from Tor.", "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", - "share_via_onionshare": "Share via OnionShare", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", @@ -166,15 +125,10 @@ "gui_status_indicator_receive_stopped": "Ready to Receive", "gui_status_indicator_receive_working": "Starting…", "gui_status_indicator_receive_started": "Receiving", - "gui_file_info": "{} Files, {}", - "gui_file_info_single": "{} File, {}", - "info_in_progress_downloads_tooltip": "{} download(s) in progress", - "info_completed_downloads_tooltip": "{} download(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", - "gui_receive_mode_warning": "Some files can hack your computer if you open them!
    Only open files from people you trust, or if you know what you're doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", @@ -183,17 +137,5 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", - "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)", - "systray_close_server_title": "OnionShare Server Closed", - "systray_close_server_message": "A user closed the server", - "systray_page_loaded_title": "OnionShare Page Loaded", - "systray_download_page_loaded_message": "A user loaded the download page", - "systray_upload_page_loaded_message": "A user loaded the upload page", - "gui_uploads": "Upload History", - "gui_uploads_window_tooltip": "Show/hide uploads", - "gui_no_uploads": "No uploads yet.", - "gui_upload_in_progress": "Upload Started {}", - "gui_upload_finished_range": "Uploaded {} to {}", - "gui_upload_finished": "Uploaded {}", - "gui_open_folder_error_nautilus": "Cannot open folder the because nautilus is not available. You can find this file here: {}" + "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)" } diff --git a/share/locale/eo.json b/share/locale/eo.json index 0745ecaf..5c0fa008 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -1,58 +1,31 @@ { "config_onion_service": "Agordas onion service je pordo {0:d}.", "preparing_files": "Preparas dosierojn por kundivido.", - "wait_for_hs": "Atendas al hidden sevice por esti preta:", - "wait_for_hs_trying": "Provas...", - "wait_for_hs_nope": "Ankoraŭ ne preta.", - "wait_for_hs_yup": "Preta!", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", "not_a_file": "{0:s} ne estas dosiero.", - "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis", "large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn", - "error_tails_invalid_port": "Malĝusta valoro, pordo-numero devas esti plena numero", - "error_tails_unknown_root": "Nekonata eraro kun Tails-root-procezo", "help_local_only": "Ne strebu uzi tor: nur por evoluado", "help_stay_open": "Lasu onion service funkcii post fino de elŝuto", - "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Protokoli erarojn sur disko", "help_filename": "Listo de dosieroj aŭ dosierujoj por kundividi", - "gui_drag_and_drop": "Ŝovu kaj metu\nla dosierojn ĉi tien", - "gui_add": "Aldoni", - "gui_delete": "Forviŝi", - "gui_choose_items": "Elekti", "gui_share_start_server": "Komenci kundividon", "gui_share_stop_server": "Ĉesigi kundividon", "gui_copy_url": "Kopii URL", "gui_copy_hidservauth": "Kopii HidServAuth", - "gui_downloads": "Elŝutoj:", - "gui_canceled": "Nuligita", - "gui_copied_url": "URL kopiita en tondujon", - "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_starting_server1": "Startigas Tor onion service...", - "gui_starting_server2": "Compressing files...", "gui_please_wait": "Bonvolu atendi...", - "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", - "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", - "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.", "gui_quit_warning_quit": "Foriri", "gui_quit_warning_dont_quit": "Ne foriri", "error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.", - "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", - "gui_menu_file_menu": "&File", - "gui_menu_settings_action": "&Settings", - "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Settings", "gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?", "gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser", @@ -63,10 +36,7 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Pasvorto", - "gui_settings_authenticate_cookie_option": "Kuketo", "gui_settings_password_label": "Pasvorto", - "gui_settings_cookie_label": "Cookie path", - "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Konservi", "gui_settings_button_cancel": "Nuligi", "settings_saved": "Agordoj konservitaj en {}", diff --git a/share/locale/es.json b/share/locale/es.json index 412fb501..705f4fbb 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,32 +1,15 @@ { - "connecting_ctrlport": "Conectando a puerto control de Tor para configurar servicio oculto en puerto {0:d}.", - "cant_connect_ctrlport": "No se pudo conectar a puerto control de Tor en puertos {0:s}. ¿Está funcionando Tor?", - "cant_connect_socksport": "No se pudo conectar al servidor SOCKS5 de Tor en el puerto {0:s}. ¿Está funcionando Tor?", "preparing_files": "Preparando los archivos para compartir.", - "wait_for_hs": "Esperando a que HS esté listo:", - "wait_for_hs_trying": "Probando...", - "wait_for_hs_nope": "No está listo todavía.", - "wait_for_hs_yup": "Listo!", "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", - "download_page_loaded": "La página de descarga está lista.", "other_page_loaded": "La URL está lista.", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", - "error_tails_invalid_port": "Valor inválido, el puerto debe ser un entero", - "error_tails_unknown_root": "Error desconocido en el proceso de Tails ejecutando como roo", - "help_tails_port": "Sólo Tails: puerto para abrir en el firewall, al levantar el servicio oculto", "help_local_only": "No intentar usar Tor: sólo para desarrollo", "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado", "help_debug": "Guardar registro de errores en el disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastre\narchivos aquí", - "gui_add": "Añadir", - "gui_delete": "Eliminar", - "gui_choose_items": "Elegir", "gui_share_start_server": "Encender el Servidor", "gui_share_stop_server": "Detener el Servidor", - "gui_copy_url": "Copiar URL", - "gui_downloads": "Descargas:", - "gui_copied_url": "Se copió la URL en el portapapeles" + "gui_copy_url": "Copiar URL" } diff --git a/share/locale/fi.json b/share/locale/fi.json index 00768528..6032b8a8 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -1,43 +1,18 @@ { - "connecting_ctrlport": "Yhdistetään Torin ohjausporttiin että saadaan salattu palvelin porttiin {0:d}.", - "cant_connect_ctrlport": "Ei voi yhdistää Torin ohjausporttiin portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", - "cant_connect_socksport": "Ei voi yhdistää Tor SOCKS5 palveluun portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", "preparing_files": "Valmistellaan tiedostoja jaettavaksi.", - "wait_for_hs": "Odotetaan piilopalvelun valmistumista:", - "wait_for_hs_trying": "Yritetään...", - "wait_for_hs_nope": "Ei vielä valmis.", - "wait_for_hs_yup": "Valmis!", "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:", "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", "not_a_file": "{0:s} Ei ole tiedosto.", - "download_page_loaded": "Lataussivu ladattu", "other_page_loaded": "URL-osoite ladattu", "closing_automatically": "Lataus valmis. Suljetaan automaattisesti", "large_filesize": "Varoitus: Isojen tiedostojen lähetys saattaa kestää tunteja", - "error_tails_invalid_port": "Väärä arvo, portti pitää olla koknaisluku", - "error_tails_unknown_root": "Tuntematon virhe Tailsissa", - "help_tails_port": "Vain Tails: portti palomuurin läpi, käynnistetään salainen palvelin", "help_local_only": "Älä käytä Toria: vain ohjelmakehitykseen", "help_stay_open": "Pidä piilopalvelu käynnissä latauksen jälkeen.", - "help_transparent_torification": "Järjestelmäni käyttää Toria läpinäkyvästi", "help_debug": "Tallentaa virheet levylle", "help_filename": "Luettele jaettavat tiedostot tai kansiot", - "gui_drag_and_drop": "Vedä ja pudota\ntiedostot tänne", - "gui_add": "Lisää", - "gui_delete": "Poista", - "gui_choose_items": "Valitse", "gui_share_start_server": "Käynnistä palvelin", "gui_share_stop_server": "Pysäytä palvelin", "gui_copy_url": "Kopioi URL-osoite", - "gui_downloads": "Lataukset:", - "gui_canceled": "Peruutettu", - "gui_copied_url": "URL-osoite kopioitu leikepöydälle", - "gui_starting_server1": "Käynnistetään Tor piilopalvelu...", - "gui_starting_server2": "Tiivistän tiedostoja...", - "gui_starting_server3": "Odotetaan Tor piilopalvelua...", "gui_please_wait": "Odota...", - "error_hs_dir_cannot_create": "Piilopalvelulle ei pystytty luomaan hakemistoa {0:s}", - "error_hs_dir_not_writable": "Piilopalvelun hakemistoon {0:s} ei voi kirjoittaa", - "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua", - "zip_progress_bar_format": "Tiivistän tiedostoja: %p%" + "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 6ec20b3b..a555dd58 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,41 +1,19 @@ { - "connecting_ctrlport": "Connexion au réseau Tor pour mettre en place un onion service sur le port {0:d}.", - "cant_connect_ctrlport": "Impossible de se connecter au port de contrôle Tor sur le port {0:s}. Est-ce que Tor tourne ?", "preparing_files": "Préparation des fichiers à partager.", - "wait_for_hs": "En attente du HS:", - "wait_for_hs_trying": "Tentative...", - "wait_for_hs_nope": "Pas encore prêt.", - "wait_for_hs_yup": "Prêt !", "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", "ctrlc_to_stop": "Ctrl-C arrête le serveur", "not_a_file": "{0:s} n'est pas un fichier.", - "download_page_loaded": "Page de téléchargement chargée", "other_page_loaded": "URL chargée", "closing_automatically": "Fermeture automatique car le téléchargement est fini", - "error_tails_invalid_port": "Valeur invalide, le port doit être un nombre entier", - "error_tails_unknown_root": "Erreur inconnue avec un processus root sur Tails", "systray_menu_exit": "Quitter", - "systray_download_started_title": "Téléchargement OnionShare Démarré", - "systray_download_started_message": "Un utilisateur télécharge vos fichiers", - "systray_download_completed_title": "Téléchargement OnionShare Complete", - "systray_download_canceled_title": "Téléchargement OnionShare Annulé", - "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", - "help_tails_port": "Seulement sur Tails: port pour ouvrir le firewall, démarrage du onion service", "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", "help_debug": "Enregistrer les erreurs sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", - "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", - "gui_add": "Ajouter", - "gui_delete": "Supprimer", - "gui_choose_items": "Sélectionnez", "gui_share_start_server": "Démarrer le serveur", "gui_share_stop_server": "Arrêter le serveur", "gui_copy_url": "Copier URL", "gui_copy_hidservauth": "Copier HidServAuth", - "gui_downloads": "Téléchargements :", - "gui_canceled": "Annulé", - "gui_copied_url": "URL copié dans le presse-papier", "gui_please_wait": "Attendez-vous...", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Ne quitter pas", diff --git a/share/locale/it.json b/share/locale/it.json index 7ad38169..f64b04d5 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,43 +1,18 @@ { - "connecting_ctrlport": "Connessione alla porta di controllo di Tor per inizializzare il servizio nascosto sulla porta {0:d}.", - "cant_connect_ctrlport": "Impossibile connettere alla porta di controllo di Tor sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", - "cant_connect_socksport": "Impossibile connettersi al server Tor SOCKS5 sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", "preparing_files": "Preparazione dei files da condividere.", - "wait_for_hs": "In attesa che l'HS sia pronto:", - "wait_for_hs_trying": "Tentativo...", - "wait_for_hs_nope": "Non è ancora pronto.", - "wait_for_hs_yup": "Pronto!", "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", - "download_page_loaded": "Pagina di Download caricata", "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica dopo aver finito il download", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "error_tails_invalid_port": "Valore non valido, la porta deve essere un numero intero", - "error_tails_unknown_root": "Errore sconosciuto con il processo Tails root", - "help_tails_port": "Solo per Tails: porta per passare il firewall, avvio del servizio nascosto", "help_local_only": "Non usare tor: è solo per lo sviluppo", "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", - "help_transparent_torification": "Il mio sistema usa tor in modo trasparente", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", - "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", - "gui_add": "Aggiungi", - "gui_delete": "Cancella", - "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", "gui_copy_url": "Copia lo URL", - "gui_downloads": "Downloads:", - "gui_canceled": "Cancellati", - "gui_copied_url": "URL Copiato nella clipboard", - "gui_starting_server1": "Avviamento del servizio nascosto Tor...", - "gui_starting_server2": "Elaborazione files...", - "gui_starting_server3": "In attesa del servizio nascosto Tor...", "gui_please_wait": "Attendere prego...", - "error_hs_dir_cannot_create": "Impossibile create la cartella per il servizio nascosto {0:s}", - "error_hs_dir_not_writable": "La cartella per il servizio nascosto {0:s} non ha i permessi di scrittura", - "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione", - "zip_progress_bar_format": "Elaborazione files: %p%" + "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione" } diff --git a/share/locale/nl.json b/share/locale/nl.json index 4031effd..19bb2b69 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -1,64 +1,33 @@ { "config_onion_service": "Onion service configureren op poort {0:d}.", "preparing_files": "Bestanden om te delen aan het voorbereiden.", - "wait_for_hs": "Wachten op gereed zijn van HS:", - "wait_for_hs_trying": "Proberen...", - "wait_for_hs_nope": "Nog niet gereed.", - "wait_for_hs_yup": "Gereed!", "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", "not_a_file": "{0:s} is geen bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", - "download_page_loaded": "Downloadpagina geladen", "other_page_loaded": "URL geladen", "close_on_timeout": "Sluit automatisch omdat timeout bereikt is", "closing_automatically": "Sluit automatisch omdat download gereed is", - "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten", "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren", - "error_tails_invalid_port": "Ongeldige waarde, poort moet een integer zijn", - "error_tails_unknown_root": "Onbekende fout met het Tails root proces", "systray_menu_exit": "Afsluiten", - "systray_download_started_title": "OnionShare download gestart", - "systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden", - "systray_download_completed_title": "OnionShare download gereed", - "systray_download_completed_message": "De gebruiker is klaar met downloaden", - "systray_download_canceled_title": "OnionShare download afgebroken", - "systray_download_canceled_message": "De gebruiker heeft de download afgebroken", "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling", "help_stay_open": "Laat verborgen service draaien nadat download gereed is", "help_shutdown_timeout": "Sluit de Onion service na N seconden", - "help_transparent_torification": "Mijn systeem gebruikt Tor als proxyserver", "help_stealth": "Maak stealth Onion service (geavanceerd)", "help_debug": "Log fouten naar harde schijf", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Pad naar een JSON configuratie bestand (optioneel)", - "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", - "gui_add": "Toevoegen", - "gui_delete": "Verwijder", - "gui_choose_items": "Kies", "gui_copy_url": "Kopieer URL", "gui_copy_hidservauth": "Kopieer HidServAuth", - "gui_downloads": "Downloads:", - "gui_canceled": "Afgebroken", - "gui_copied_url": "URL gekopieerd naar klembord", - "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", - "gui_starting_server1": "Tor onion service wordt gestart...", - "gui_starting_server2": "Bestanden verwerken...", "gui_please_wait": "Moment geduld...", - "error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken", - "error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar", "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie", - "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Niet afsluiten", "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.", - "zip_progress_bar_format": "Bestanden verwerken: %p%", "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", @@ -84,13 +53,10 @@ "gui_settings_authenticate_label": "Tor authenticatie opties", "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Wachtwoord", - "gui_settings_cookie_label": "Cookie pad", "gui_settings_button_save": "Opslaan", "gui_settings_button_cancel": "Annuleren", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout_choice": "Auto-stop timer instellen?", "gui_settings_shutdown_timeout": "Stop delen om:", "settings_saved": "Instellingen opgeslagen in {}", "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.", @@ -102,7 +68,6 @@ "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.", "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.", - "settings_error_bundled_tor_canceled": "Het Tor is afgesloten voordat het kon verbinden.", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}", "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.", @@ -117,6 +82,5 @@ "gui_tor_connection_error_settings": "Probeer de instellingen hoe OnionShare verbind met het Tor network aan te passen in Instellingen.", "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.", "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.", - "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.", - "share_via_onionshare": "Deel via OnionShare" + "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw." } diff --git a/share/locale/no.json b/share/locale/no.json index 8b131038..6b277353 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,10 +1,6 @@ { - "connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0:d}.", - "cant_connect_ctrlport": "Klarte ikke å koble til Tors kontroll-porter {0:s}. Sjekk at Tor kjører.", "give_this_url": "Gi personen du vil sende filen til denne URL-en:", "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", - "gui_copied_url": "Kopierte URL-en til utklippstavlen", - "download_page_loaded": "Nedlastingsside lastet", "other_page_loaded": "En annen side har blitt lastet" } diff --git a/share/locale/pt.json b/share/locale/pt.json index 71391957..0aefd847 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,10 +1,6 @@ { - "connecting_ctrlport": "Conectando-se à porta de controle Tor para configurar serviço escondido na porta {0:d}.", - "cant_connect_ctrlport": "Não pode conectar à porta de controle Tor na porta {0:s}. O Tor está rodando?", "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", "not_a_file": "{0:s} não é um arquivo.", - "gui_copied_url": "URL foi copiado para área de transferência", - "download_page_loaded": "Página de download carregada", "other_page_loaded": "Outra página tem sido carregada" } diff --git a/share/locale/ru.json b/share/locale/ru.json index 193c158d..e1c59721 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,11 +1,7 @@ { - "connecting_ctrlport": "Соединяемся с контрольным портом Tor для создания скрытого сервиса на порту {0:d}.", - "cant_connect_ctrlport": "Невозможно соединиться с контрольным портом Tor на порту {0:s}. Tor запущен?", "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", "not_a_file": "{0:s} не является файлом.", - "gui_copied_url": "Ссылка скопирована в буфер обмена", - "download_page_loaded": "Страница закачки загружена", "other_page_loaded": "Другая страница была загружена", "gui_copy_url": "Скопировать ссылку" } diff --git a/share/locale/tr.json b/share/locale/tr.json index d8097909..2f041233 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,43 +1,18 @@ { - "connecting_ctrlport": "{0:d} portundaki gizli hizmet(GH) kurulumu için Tor kontrol portuna bağlanıyor.", - "cant_connect_ctrlport": "Tor kontrol {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", - "cant_connect_socksport": "Tor SOCKS5 sunucu {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", - "wait_for_hs": "GH hazır olması bekleniyor:", - "wait_for_hs_trying": "Deneniyor...", - "wait_for_hs_nope": "Henüz hazır değil.", - "wait_for_hs_yup": "Hazır!", "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", - "download_page_loaded": "İndirme sayfası yüklendi", "other_page_loaded": "Diğer sayfa yüklendi", "closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor", "large_filesize": "Uyarı: Büyük dosyaların gönderimi saatler sürebilir", - "error_tails_invalid_port": "Geçersiz değer, port sayı olmalıdır", - "error_tails_unknown_root": "Tails ana işlemi ile ilgili bilinmeyen hata", - "help_tails_port": "Sadece Tails: port for opening firewall, starting onion service", "help_local_only": "Tor kullanmaya kalkışmayın: sadece geliştirme için", "help_stay_open": "İndirme tamamlandıktan sonra gizli hizmeti çalıştırmaya devam et", - "help_transparent_torification": "Sistemim apaçık torlu", "help_debug": "Hata kayıtlarını diske kaydet", "help_filename": "Paylaşmak için dosya ve klasörler listesi", - "gui_drag_and_drop": "Dosyaları buraya\n Sürükle ve Bırak", - "gui_add": "Ekle", - "gui_delete": "Sil", - "gui_choose_items": "Seç", "gui_share_start_server": "Paylaşımı Başlat", "gui_share_stop_server": "Paylaşımı Durdur", "gui_copy_url": "URL Kopyala", - "gui_downloads": "İndirilenler:", - "gui_canceled": "İptal edilen", - "gui_copied_url": "Panoya kopyalanan URL", - "gui_starting_server1": "Tor gizli hizmeti başlatılıyor...", - "gui_starting_server2": "Dosyalar hazırlanıyor...", - "gui_starting_server3": "Tor gizli hizmeti bekleniyor...", "gui_please_wait": "Lütfen bekleyin...", - "error_hs_dir_cannot_create": "Gizli hizmet klasörü {0:s} oluşturulamıyor", - "error_hs_dir_not_writable": "Gizle hizmet klasörü {0:s} yazılabilir değil", - "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor", - "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" + "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor" } -- cgit v1.2.3-54-g00ecf From e343aa194e8e5f1df748b7174a67b62ef480a048 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 16:26:10 +1000 Subject: Revert "#681 remove obsolete strings" This reverts commit e4d1f8b8f67a138d6b250ed178576eac892984eb. --- share/locale/cs.json | 30 ++++++++++++++++++++++++++ share/locale/da.json | 39 +++++++++++++++++++++++++++++++++- share/locale/de.json | 17 +++++++++++++++ share/locale/en.json | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++- share/locale/eo.json | 30 ++++++++++++++++++++++++++ share/locale/es.json | 19 ++++++++++++++++- share/locale/fi.json | 27 ++++++++++++++++++++++- share/locale/fr.json | 22 +++++++++++++++++++ share/locale/it.json | 27 ++++++++++++++++++++++- share/locale/nl.json | 38 ++++++++++++++++++++++++++++++++- share/locale/no.json | 4 ++++ share/locale/pt.json | 4 ++++ share/locale/ru.json | 4 ++++ share/locale/tr.json | 27 ++++++++++++++++++++++- 14 files changed, 341 insertions(+), 7 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 9e151dd6..aaa80d1b 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -1,31 +1,58 @@ { "config_onion_service": "Nastavuji onion service na portu {0:d}.", "preparing_files": "Připravuji soubory ke sdílení.", + "wait_for_hs": "Čekám na HS až bude připravena:", + "wait_for_hs_trying": "Zkouším...", + "wait_for_hs_nope": "Ještě nepřipraven.", + "wait_for_hs_yup": "Připraven!", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", + "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", "large_filesize": "Varování: Posílání velkých souborů může trvat hodiny", + "error_tails_invalid_port": "Nesprávná hodnota, port musí být celé číslo", + "error_tails_unknown_root": "Neznámá chyba s Tails root procesem", "help_local_only": "Nepoužívat Tor: jen pro vývoj", "help_stay_open": "Nechat běžet onion service po skončení stahování", + "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Zaznamenat chyby na disk", "help_filename": "Seznam souborů a složek ke sdílení", + "gui_drag_and_drop": "Táhni a pusť\nsoubory sem", + "gui_add": "Přidat", + "gui_delete": "Smazat", + "gui_choose_items": "Vybrat", "gui_share_start_server": "Spustit sdílení", "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", + "gui_downloads": "Stahování:", + "gui_canceled": "Zrušeno", + "gui_copied_url": "URL zkopírováno do schránky", + "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", + "gui_starting_server1": "Spouštím Tor onion service...", + "gui_starting_server2": "Zpracovávám soubory...", "gui_please_wait": "Prosím čekejte...", + "error_hs_dir_cannot_create": "Nejde vytvořit složka onion service {0:s}", + "error_hs_dir_not_writable": "nejde zapisovat do složky onion service {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", + "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Jste si jistí, že chcete odejít?\nURL, kterou sdílíte poté nebude existovat.", "gui_quit_warning_quit": "Zavřít", "gui_quit_warning_dont_quit": "Zůstat", "error_rate_limit": "Útočník možná zkouší uhodnout vaši URL. Abychom tomu předešli, OnionShare automaticky zastavil server. Pro sdílení souborů ho musíte spustit znovu a sdílet novou URL.", + "zip_progress_bar_format": "Zpracovávám soubory: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.", + "gui_menu_file_menu": "&File", + "gui_menu_settings_action": "&Settings", + "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", @@ -36,7 +63,10 @@ "gui_settings_authenticate_label": "Autentizační možnosti Toru", "gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace", "gui_settings_authenticate_password_option": "Heslo", + "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Heslo", + "gui_settings_cookie_label": "Cesta ke cookie", + "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Uložit", "gui_settings_button_cancel": "Zrušit", "settings_saved": "Nastavení uloženo do {}", diff --git a/share/locale/da.json b/share/locale/da.json index ded2d61c..d36f7035 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,35 +1,66 @@ { "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", "preparing_files": "Forbereder filer som skal deles.", + "wait_for_hs": "Venter på at HS bliver klar:", + "wait_for_hs_trying": "Prøver...", + "wait_for_hs_nope": "Endnu ikke klar.", + "wait_for_hs_yup": "Klar!", "give_this_url": "Giv denne URL til personen du sender filen til:", "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", "not_a_file": "{0:s} er ikke en gyldig fil.", "not_a_readable_file": "{0:s} er ikke en læsbar fil.", "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", + "download_page_loaded": "Downloadside indlæst", "other_page_loaded": "URL indlæst", "close_on_timeout": "Lukker automatisk da timeout er nået", "closing_automatically": "Lukker automatisk da download er færdig", + "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop", "large_filesize": "Advarsel: Det kan tage timer at sende store filer", + "error_tails_invalid_port": "Ugyldig værdi, port skal være et heltal", + "error_tails_unknown_root": "Ukendt fejl med Tails-rodproces", "systray_menu_exit": "Afslut", + "systray_download_started_title": "OnionShare-download startet", + "systray_download_started_message": "En bruger startede download af dine filer", + "systray_download_completed_title": "OnionShare-download færdig", + "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", + "systray_download_canceled_title": "OnionShare-download annulleret", + "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Undlad at bruge tor: kun til udvikling", "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder", + "help_transparent_torification": "Mit system er gennemsigtigt torifiseret", "help_stealth": "Opret usynlig onion-tjeneste (avanceret)", "help_debug": "Log programfejl til stdout, og log webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", + "gui_drag_and_drop": "Træk og slip\nfiler her", + "gui_add": "Tilføj", + "gui_delete": "Slet", + "gui_choose_items": "Vælg", "gui_share_start_server": "Start deling", "gui_share_stop_server": "Stop deling", "gui_copy_url": "Kopiér URL", "gui_copy_hidservauth": "Kopiér HidServAuth", + "gui_downloads": "Downloads:", + "gui_canceled": "Annulleret", + "gui_copied_url": "Kopierede URL til udklipsholder", + "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", + "gui_starting_server1": "Starter Tor onion-tjeneste...", + "gui_starting_server2": "Databehandler filer...", "gui_please_wait": "Vent venligst...", + "error_hs_dir_cannot_create": "Kan ikke oprette onion-tjenestens mappe {0:s}", + "error_hs_dir_not_writable": "onion-tjenestens mappe {0:s} er skrivebeskyttet", "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse", + "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", + "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Afslut ikke", "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.", + "zip_progress_bar_format": "Databehandler filer: %p%", "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", @@ -56,7 +87,9 @@ "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", "gui_settings_authenticate_password_option": "Adgangskode", + "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Adgangskode", + "gui_settings_cookie_label": "Cookiesti", "gui_settings_tor_bridges": "Understøttelse af Tor-bro", "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter", @@ -67,6 +100,7 @@ "gui_settings_button_save": "Gem", "gui_settings_button_cancel": "Annuller", "gui_settings_button_help": "Hjælp", + "gui_settings_shutdown_timeout_choice": "Sæt timer til automatisk stop?", "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", @@ -78,6 +112,7 @@ "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.", "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", + "settings_error_bundled_tor_canceled": "Tor-processen lukkede inden den blev færdig med at oprette forbindelse.", "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}", "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", @@ -94,5 +129,7 @@ "gui_tor_connection_lost": "Afbryder forbindelsen fra Tor.", "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)" + "share_via_onionshare": "Del via OnionShare", + "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)", + "persistent_url_in_use": "Denne deling bruger en vedvarende URL" } diff --git a/share/locale/de.json b/share/locale/de.json index b5925b8e..8e87b89b 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,17 +1,34 @@ { + "connecting_ctrlport": "Verbinde zum Tor-Kontrollport um den versteckten Dienst auf Port {0:d} laufen zu lassen.", + "cant_connect_ctrlport": "Konnte keine Verbindung zum Tor-Kontrollport auf Port {0:s} aufbauen. Läuft Tor?", + "cant_connect_socksport": "Konnte keine Verbindung zum Tor SOCKS5 Server auf Port {0:s} herstellen. OnionShare setzt voraus dass Tor Browser im Hintergrund läuft. Wenn du noch ihn noch noch nicht hast kannst du ihn unter https://www.torproject.org/ herunterladen.", "preparing_files": "Dateien werden vorbereitet.", + "wait_for_hs": "Warte auf HS:", + "wait_for_hs_trying": "Verbindungsversuch...", + "wait_for_hs_nope": "Noch nicht bereit.", + "wait_for_hs_yup": "Bereit!", "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", + "download_page_loaded": "Seite geladen", "other_page_loaded": "URL geladen", "closing_automatically": "Halte automatisch an, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", + "error_tails_invalid_port": "Ungültiger Wert, Port muss eine ganze Zahl sein", + "error_tails_unknown_root": "Unbekannter Fehler mit Tails root Prozess", + "help_tails_port": "Nur für Tails: Port um den Firewall zu öffnen, starte onion service", "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Fehler auf Festplatte schreiben", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", + "gui_drag_and_drop": "Drag & drop\nDateien hier", + "gui_add": "Hinzufügen", + "gui_delete": "Löschen", + "gui_choose_items": "Auswählen", "gui_share_start_server": "Server starten", "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", + "gui_downloads": "Downloads:", + "gui_copied_url": "URL wurde in die Zwischenablage kopiert", "gui_please_wait": "Bitte warten..." } diff --git a/share/locale/en.json b/share/locale/en.json index cbb444ec..b1d247d9 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -1,6 +1,10 @@ { "config_onion_service": "Configuring onion service on port {0:d}.", "preparing_files": "Preparing files to share.", + "wait_for_hs": "Waiting for HS to be ready:", + "wait_for_hs_trying": "Trying…", + "wait_for_hs_nope": "Not ready yet.", + "wait_for_hs_yup": "Ready!", "give_this_url": "Give this address to the person you're sending the file to:", "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", "give_this_url_receive": "Give this address to the people sending you files:", @@ -13,8 +17,21 @@ "other_page_loaded": "Address loaded", "close_on_timeout": "Stopped because timer expired", "closing_automatically": "Stopped because download finished", + "timeout_download_still_running": "Waiting for download to complete", "large_filesize": "Warning: Sending large files could take hours", + "error_tails_invalid_port": "Invalid value, port must be a regular number", + "error_tails_unknown_root": "Unknown error with Tails root process", "systray_menu_exit": "Quit", + "systray_download_started_title": "OnionShare Download Started", + "systray_download_started_message": "A user started downloading your files", + "systray_download_completed_title": "OnionShare Download Finished", + "systray_download_completed_message": "The user finished downloading your files", + "systray_download_canceled_title": "OnionShare Download Canceled", + "systray_download_canceled_message": "The user canceled the download", + "systray_upload_started_title": "OnionShare Upload Started", + "systray_upload_started_message": "A user started uploading files to your computer", + "systray_upload_completed_title": "OnionShare Upload Finished", + "systray_upload_completed_message": "The user finished uploading files to your computer", "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", @@ -23,18 +40,37 @@ "help_debug": "Log application errors to stdout, and log web errors to disk", "help_filename": "List of files or folders to share", "help_config": "Path to a custom JSON config file (optional)", + "gui_drag_and_drop": "Drag and drop files and folders\nto start sharing", + "gui_add": "Add", + "gui_delete": "Delete", + "gui_choose_items": "Choose", "gui_share_start_server": "Start Sharing", "gui_share_stop_server": "Stop Sharing", + "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", "gui_share_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", "gui_receive_start_server": "Start Receive Mode", "gui_receive_stop_server": "Stop Receive Mode", + "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Receive mode will expire automatically at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", + "gui_downloads": "Download History", + "gui_downloads_window_tooltip": "Show/hide downloads", + "gui_no_downloads": "No downloads yet.", + "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare address", + "gui_copied_url": "The OnionShare address has been copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", + "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", + "gui_starting_server1": "Starting Tor onion service…", + "gui_starting_server2": "Compressing files…", "gui_please_wait": "Starting… Click to cancel", + "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", + "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", + "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Transfer in Progress", "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", @@ -42,6 +78,7 @@ "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", @@ -68,7 +105,9 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", + "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Password", + "gui_settings_cookie_label": "Cookie path", "gui_settings_tor_bridges": "Tor Bridge support", "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", @@ -96,6 +135,7 @@ "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", + "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", @@ -112,6 +152,7 @@ "gui_tor_connection_lost": "Disconnected from Tor.", "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", + "share_via_onionshare": "Share via OnionShare", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", @@ -125,10 +166,15 @@ "gui_status_indicator_receive_stopped": "Ready to Receive", "gui_status_indicator_receive_working": "Starting…", "gui_status_indicator_receive_started": "Receiving", + "gui_file_info": "{} Files, {}", + "gui_file_info_single": "{} File, {}", + "info_in_progress_downloads_tooltip": "{} download(s) in progress", + "info_completed_downloads_tooltip": "{} download(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", + "gui_receive_mode_warning": "Some files can hack your computer if you open them!
    Only open files from people you trust, or if you know what you're doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", @@ -137,5 +183,17 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", - "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)" + "gui_settings_receive_public_mode_checkbox": "Receive mode is open to the public\n(don't prevent people from guessing the OnionShare address)", + "systray_close_server_title": "OnionShare Server Closed", + "systray_close_server_message": "A user closed the server", + "systray_page_loaded_title": "OnionShare Page Loaded", + "systray_download_page_loaded_message": "A user loaded the download page", + "systray_upload_page_loaded_message": "A user loaded the upload page", + "gui_uploads": "Upload History", + "gui_uploads_window_tooltip": "Show/hide uploads", + "gui_no_uploads": "No uploads yet.", + "gui_upload_in_progress": "Upload Started {}", + "gui_upload_finished_range": "Uploaded {} to {}", + "gui_upload_finished": "Uploaded {}", + "gui_open_folder_error_nautilus": "Cannot open folder the because nautilus is not available. You can find this file here: {}" } diff --git a/share/locale/eo.json b/share/locale/eo.json index 5c0fa008..0745ecaf 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -1,31 +1,58 @@ { "config_onion_service": "Agordas onion service je pordo {0:d}.", "preparing_files": "Preparas dosierojn por kundivido.", + "wait_for_hs": "Atendas al hidden sevice por esti preta:", + "wait_for_hs_trying": "Provas...", + "wait_for_hs_nope": "Ankoraŭ ne preta.", + "wait_for_hs_yup": "Preta!", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", "not_a_file": "{0:s} ne estas dosiero.", + "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis", "large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn", + "error_tails_invalid_port": "Malĝusta valoro, pordo-numero devas esti plena numero", + "error_tails_unknown_root": "Nekonata eraro kun Tails-root-procezo", "help_local_only": "Ne strebu uzi tor: nur por evoluado", "help_stay_open": "Lasu onion service funkcii post fino de elŝuto", + "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Protokoli erarojn sur disko", "help_filename": "Listo de dosieroj aŭ dosierujoj por kundividi", + "gui_drag_and_drop": "Ŝovu kaj metu\nla dosierojn ĉi tien", + "gui_add": "Aldoni", + "gui_delete": "Forviŝi", + "gui_choose_items": "Elekti", "gui_share_start_server": "Komenci kundividon", "gui_share_stop_server": "Ĉesigi kundividon", "gui_copy_url": "Kopii URL", "gui_copy_hidservauth": "Kopii HidServAuth", + "gui_downloads": "Elŝutoj:", + "gui_canceled": "Nuligita", + "gui_copied_url": "URL kopiita en tondujon", + "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", + "gui_starting_server1": "Startigas Tor onion service...", + "gui_starting_server2": "Compressing files...", "gui_please_wait": "Bonvolu atendi...", + "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", + "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", + "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Ĉu vi certas ke vi volas foriri?\nLa URL, kiun vi kundividas ne plu ekzistos.", "gui_quit_warning_quit": "Foriri", "gui_quit_warning_dont_quit": "Ne foriri", "error_rate_limit": "Iu atankanto povas provi diveni vian URL. Por eviti tion, OnionShare aŭtomate haltis la servilon. Por kundividi la dosierojn vi devas starti ĝin denove kaj kundividi la novan URL.", + "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", + "gui_menu_file_menu": "&File", + "gui_menu_settings_action": "&Settings", + "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Settings", "gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?", "gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser", @@ -36,7 +63,10 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Pasvorto", + "gui_settings_authenticate_cookie_option": "Kuketo", "gui_settings_password_label": "Pasvorto", + "gui_settings_cookie_label": "Cookie path", + "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Konservi", "gui_settings_button_cancel": "Nuligi", "settings_saved": "Agordoj konservitaj en {}", diff --git a/share/locale/es.json b/share/locale/es.json index 705f4fbb..412fb501 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,15 +1,32 @@ { + "connecting_ctrlport": "Conectando a puerto control de Tor para configurar servicio oculto en puerto {0:d}.", + "cant_connect_ctrlport": "No se pudo conectar a puerto control de Tor en puertos {0:s}. ¿Está funcionando Tor?", + "cant_connect_socksport": "No se pudo conectar al servidor SOCKS5 de Tor en el puerto {0:s}. ¿Está funcionando Tor?", "preparing_files": "Preparando los archivos para compartir.", + "wait_for_hs": "Esperando a que HS esté listo:", + "wait_for_hs_trying": "Probando...", + "wait_for_hs_nope": "No está listo todavía.", + "wait_for_hs_yup": "Listo!", "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", + "download_page_loaded": "La página de descarga está lista.", "other_page_loaded": "La URL está lista.", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", + "error_tails_invalid_port": "Valor inválido, el puerto debe ser un entero", + "error_tails_unknown_root": "Error desconocido en el proceso de Tails ejecutando como roo", + "help_tails_port": "Sólo Tails: puerto para abrir en el firewall, al levantar el servicio oculto", "help_local_only": "No intentar usar Tor: sólo para desarrollo", "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado", "help_debug": "Guardar registro de errores en el disco", "help_filename": "Lista de archivos o carpetas para compartir", + "gui_drag_and_drop": "Arrastre\narchivos aquí", + "gui_add": "Añadir", + "gui_delete": "Eliminar", + "gui_choose_items": "Elegir", "gui_share_start_server": "Encender el Servidor", "gui_share_stop_server": "Detener el Servidor", - "gui_copy_url": "Copiar URL" + "gui_copy_url": "Copiar URL", + "gui_downloads": "Descargas:", + "gui_copied_url": "Se copió la URL en el portapapeles" } diff --git a/share/locale/fi.json b/share/locale/fi.json index 6032b8a8..00768528 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -1,18 +1,43 @@ { + "connecting_ctrlport": "Yhdistetään Torin ohjausporttiin että saadaan salattu palvelin porttiin {0:d}.", + "cant_connect_ctrlport": "Ei voi yhdistää Torin ohjausporttiin portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", + "cant_connect_socksport": "Ei voi yhdistää Tor SOCKS5 palveluun portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", "preparing_files": "Valmistellaan tiedostoja jaettavaksi.", + "wait_for_hs": "Odotetaan piilopalvelun valmistumista:", + "wait_for_hs_trying": "Yritetään...", + "wait_for_hs_nope": "Ei vielä valmis.", + "wait_for_hs_yup": "Valmis!", "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:", "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", "not_a_file": "{0:s} Ei ole tiedosto.", + "download_page_loaded": "Lataussivu ladattu", "other_page_loaded": "URL-osoite ladattu", "closing_automatically": "Lataus valmis. Suljetaan automaattisesti", "large_filesize": "Varoitus: Isojen tiedostojen lähetys saattaa kestää tunteja", + "error_tails_invalid_port": "Väärä arvo, portti pitää olla koknaisluku", + "error_tails_unknown_root": "Tuntematon virhe Tailsissa", + "help_tails_port": "Vain Tails: portti palomuurin läpi, käynnistetään salainen palvelin", "help_local_only": "Älä käytä Toria: vain ohjelmakehitykseen", "help_stay_open": "Pidä piilopalvelu käynnissä latauksen jälkeen.", + "help_transparent_torification": "Järjestelmäni käyttää Toria läpinäkyvästi", "help_debug": "Tallentaa virheet levylle", "help_filename": "Luettele jaettavat tiedostot tai kansiot", + "gui_drag_and_drop": "Vedä ja pudota\ntiedostot tänne", + "gui_add": "Lisää", + "gui_delete": "Poista", + "gui_choose_items": "Valitse", "gui_share_start_server": "Käynnistä palvelin", "gui_share_stop_server": "Pysäytä palvelin", "gui_copy_url": "Kopioi URL-osoite", + "gui_downloads": "Lataukset:", + "gui_canceled": "Peruutettu", + "gui_copied_url": "URL-osoite kopioitu leikepöydälle", + "gui_starting_server1": "Käynnistetään Tor piilopalvelu...", + "gui_starting_server2": "Tiivistän tiedostoja...", + "gui_starting_server3": "Odotetaan Tor piilopalvelua...", "gui_please_wait": "Odota...", - "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua" + "error_hs_dir_cannot_create": "Piilopalvelulle ei pystytty luomaan hakemistoa {0:s}", + "error_hs_dir_not_writable": "Piilopalvelun hakemistoon {0:s} ei voi kirjoittaa", + "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua", + "zip_progress_bar_format": "Tiivistän tiedostoja: %p%" } diff --git a/share/locale/fr.json b/share/locale/fr.json index a555dd58..6ec20b3b 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,19 +1,41 @@ { + "connecting_ctrlport": "Connexion au réseau Tor pour mettre en place un onion service sur le port {0:d}.", + "cant_connect_ctrlport": "Impossible de se connecter au port de contrôle Tor sur le port {0:s}. Est-ce que Tor tourne ?", "preparing_files": "Préparation des fichiers à partager.", + "wait_for_hs": "En attente du HS:", + "wait_for_hs_trying": "Tentative...", + "wait_for_hs_nope": "Pas encore prêt.", + "wait_for_hs_yup": "Prêt !", "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", "ctrlc_to_stop": "Ctrl-C arrête le serveur", "not_a_file": "{0:s} n'est pas un fichier.", + "download_page_loaded": "Page de téléchargement chargée", "other_page_loaded": "URL chargée", "closing_automatically": "Fermeture automatique car le téléchargement est fini", + "error_tails_invalid_port": "Valeur invalide, le port doit être un nombre entier", + "error_tails_unknown_root": "Erreur inconnue avec un processus root sur Tails", "systray_menu_exit": "Quitter", + "systray_download_started_title": "Téléchargement OnionShare Démarré", + "systray_download_started_message": "Un utilisateur télécharge vos fichiers", + "systray_download_completed_title": "Téléchargement OnionShare Complete", + "systray_download_canceled_title": "Téléchargement OnionShare Annulé", + "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", + "help_tails_port": "Seulement sur Tails: port pour ouvrir le firewall, démarrage du onion service", "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", "help_debug": "Enregistrer les erreurs sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", + "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", + "gui_add": "Ajouter", + "gui_delete": "Supprimer", + "gui_choose_items": "Sélectionnez", "gui_share_start_server": "Démarrer le serveur", "gui_share_stop_server": "Arrêter le serveur", "gui_copy_url": "Copier URL", "gui_copy_hidservauth": "Copier HidServAuth", + "gui_downloads": "Téléchargements :", + "gui_canceled": "Annulé", + "gui_copied_url": "URL copié dans le presse-papier", "gui_please_wait": "Attendez-vous...", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Ne quitter pas", diff --git a/share/locale/it.json b/share/locale/it.json index f64b04d5..7ad38169 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,18 +1,43 @@ { + "connecting_ctrlport": "Connessione alla porta di controllo di Tor per inizializzare il servizio nascosto sulla porta {0:d}.", + "cant_connect_ctrlport": "Impossibile connettere alla porta di controllo di Tor sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", + "cant_connect_socksport": "Impossibile connettersi al server Tor SOCKS5 sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", "preparing_files": "Preparazione dei files da condividere.", + "wait_for_hs": "In attesa che l'HS sia pronto:", + "wait_for_hs_trying": "Tentativo...", + "wait_for_hs_nope": "Non è ancora pronto.", + "wait_for_hs_yup": "Pronto!", "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", + "download_page_loaded": "Pagina di Download caricata", "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica dopo aver finito il download", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", + "error_tails_invalid_port": "Valore non valido, la porta deve essere un numero intero", + "error_tails_unknown_root": "Errore sconosciuto con il processo Tails root", + "help_tails_port": "Solo per Tails: porta per passare il firewall, avvio del servizio nascosto", "help_local_only": "Non usare tor: è solo per lo sviluppo", "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", + "help_transparent_torification": "Il mio sistema usa tor in modo trasparente", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", + "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", + "gui_add": "Aggiungi", + "gui_delete": "Cancella", + "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", "gui_copy_url": "Copia lo URL", + "gui_downloads": "Downloads:", + "gui_canceled": "Cancellati", + "gui_copied_url": "URL Copiato nella clipboard", + "gui_starting_server1": "Avviamento del servizio nascosto Tor...", + "gui_starting_server2": "Elaborazione files...", + "gui_starting_server3": "In attesa del servizio nascosto Tor...", "gui_please_wait": "Attendere prego...", - "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione" + "error_hs_dir_cannot_create": "Impossibile create la cartella per il servizio nascosto {0:s}", + "error_hs_dir_not_writable": "La cartella per il servizio nascosto {0:s} non ha i permessi di scrittura", + "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione", + "zip_progress_bar_format": "Elaborazione files: %p%" } diff --git a/share/locale/nl.json b/share/locale/nl.json index 19bb2b69..4031effd 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -1,33 +1,64 @@ { "config_onion_service": "Onion service configureren op poort {0:d}.", "preparing_files": "Bestanden om te delen aan het voorbereiden.", + "wait_for_hs": "Wachten op gereed zijn van HS:", + "wait_for_hs_trying": "Proberen...", + "wait_for_hs_nope": "Nog niet gereed.", + "wait_for_hs_yup": "Gereed!", "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", "not_a_file": "{0:s} is geen bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", + "download_page_loaded": "Downloadpagina geladen", "other_page_loaded": "URL geladen", "close_on_timeout": "Sluit automatisch omdat timeout bereikt is", "closing_automatically": "Sluit automatisch omdat download gereed is", + "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten", "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren", + "error_tails_invalid_port": "Ongeldige waarde, poort moet een integer zijn", + "error_tails_unknown_root": "Onbekende fout met het Tails root proces", "systray_menu_exit": "Afsluiten", + "systray_download_started_title": "OnionShare download gestart", + "systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden", + "systray_download_completed_title": "OnionShare download gereed", + "systray_download_completed_message": "De gebruiker is klaar met downloaden", + "systray_download_canceled_title": "OnionShare download afgebroken", + "systray_download_canceled_message": "De gebruiker heeft de download afgebroken", "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling", "help_stay_open": "Laat verborgen service draaien nadat download gereed is", "help_shutdown_timeout": "Sluit de Onion service na N seconden", + "help_transparent_torification": "Mijn systeem gebruikt Tor als proxyserver", "help_stealth": "Maak stealth Onion service (geavanceerd)", "help_debug": "Log fouten naar harde schijf", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Pad naar een JSON configuratie bestand (optioneel)", + "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", + "gui_add": "Toevoegen", + "gui_delete": "Verwijder", + "gui_choose_items": "Kies", "gui_copy_url": "Kopieer URL", "gui_copy_hidservauth": "Kopieer HidServAuth", + "gui_downloads": "Downloads:", + "gui_canceled": "Afgebroken", + "gui_copied_url": "URL gekopieerd naar klembord", + "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", + "gui_starting_server1": "Tor onion service wordt gestart...", + "gui_starting_server2": "Bestanden verwerken...", "gui_please_wait": "Moment geduld...", + "error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken", + "error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar", "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie", + "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", + "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Niet afsluiten", "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.", + "zip_progress_bar_format": "Bestanden verwerken: %p%", "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", @@ -53,10 +84,13 @@ "gui_settings_authenticate_label": "Tor authenticatie opties", "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", + "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Wachtwoord", + "gui_settings_cookie_label": "Cookie pad", "gui_settings_button_save": "Opslaan", "gui_settings_button_cancel": "Annuleren", "gui_settings_button_help": "Help", + "gui_settings_shutdown_timeout_choice": "Auto-stop timer instellen?", "gui_settings_shutdown_timeout": "Stop delen om:", "settings_saved": "Instellingen opgeslagen in {}", "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.", @@ -68,6 +102,7 @@ "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.", "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.", + "settings_error_bundled_tor_canceled": "Het Tor is afgesloten voordat het kon verbinden.", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}", "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.", @@ -82,5 +117,6 @@ "gui_tor_connection_error_settings": "Probeer de instellingen hoe OnionShare verbind met het Tor network aan te passen in Instellingen.", "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.", "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.", - "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw." + "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.", + "share_via_onionshare": "Deel via OnionShare" } diff --git a/share/locale/no.json b/share/locale/no.json index 6b277353..8b131038 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,6 +1,10 @@ { + "connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0:d}.", + "cant_connect_ctrlport": "Klarte ikke å koble til Tors kontroll-porter {0:s}. Sjekk at Tor kjører.", "give_this_url": "Gi personen du vil sende filen til denne URL-en:", "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", + "gui_copied_url": "Kopierte URL-en til utklippstavlen", + "download_page_loaded": "Nedlastingsside lastet", "other_page_loaded": "En annen side har blitt lastet" } diff --git a/share/locale/pt.json b/share/locale/pt.json index 0aefd847..71391957 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,6 +1,10 @@ { + "connecting_ctrlport": "Conectando-se à porta de controle Tor para configurar serviço escondido na porta {0:d}.", + "cant_connect_ctrlport": "Não pode conectar à porta de controle Tor na porta {0:s}. O Tor está rodando?", "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", "not_a_file": "{0:s} não é um arquivo.", + "gui_copied_url": "URL foi copiado para área de transferência", + "download_page_loaded": "Página de download carregada", "other_page_loaded": "Outra página tem sido carregada" } diff --git a/share/locale/ru.json b/share/locale/ru.json index e1c59721..193c158d 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,7 +1,11 @@ { + "connecting_ctrlport": "Соединяемся с контрольным портом Tor для создания скрытого сервиса на порту {0:d}.", + "cant_connect_ctrlport": "Невозможно соединиться с контрольным портом Tor на порту {0:s}. Tor запущен?", "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", "not_a_file": "{0:s} не является файлом.", + "gui_copied_url": "Ссылка скопирована в буфер обмена", + "download_page_loaded": "Страница закачки загружена", "other_page_loaded": "Другая страница была загружена", "gui_copy_url": "Скопировать ссылку" } diff --git a/share/locale/tr.json b/share/locale/tr.json index 2f041233..d8097909 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,18 +1,43 @@ { + "connecting_ctrlport": "{0:d} portundaki gizli hizmet(GH) kurulumu için Tor kontrol portuna bağlanıyor.", + "cant_connect_ctrlport": "Tor kontrol {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", + "cant_connect_socksport": "Tor SOCKS5 sunucu {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", + "wait_for_hs": "GH hazır olması bekleniyor:", + "wait_for_hs_trying": "Deneniyor...", + "wait_for_hs_nope": "Henüz hazır değil.", + "wait_for_hs_yup": "Hazır!", "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", + "download_page_loaded": "İndirme sayfası yüklendi", "other_page_loaded": "Diğer sayfa yüklendi", "closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor", "large_filesize": "Uyarı: Büyük dosyaların gönderimi saatler sürebilir", + "error_tails_invalid_port": "Geçersiz değer, port sayı olmalıdır", + "error_tails_unknown_root": "Tails ana işlemi ile ilgili bilinmeyen hata", + "help_tails_port": "Sadece Tails: port for opening firewall, starting onion service", "help_local_only": "Tor kullanmaya kalkışmayın: sadece geliştirme için", "help_stay_open": "İndirme tamamlandıktan sonra gizli hizmeti çalıştırmaya devam et", + "help_transparent_torification": "Sistemim apaçık torlu", "help_debug": "Hata kayıtlarını diske kaydet", "help_filename": "Paylaşmak için dosya ve klasörler listesi", + "gui_drag_and_drop": "Dosyaları buraya\n Sürükle ve Bırak", + "gui_add": "Ekle", + "gui_delete": "Sil", + "gui_choose_items": "Seç", "gui_share_start_server": "Paylaşımı Başlat", "gui_share_stop_server": "Paylaşımı Durdur", "gui_copy_url": "URL Kopyala", + "gui_downloads": "İndirilenler:", + "gui_canceled": "İptal edilen", + "gui_copied_url": "Panoya kopyalanan URL", + "gui_starting_server1": "Tor gizli hizmeti başlatılıyor...", + "gui_starting_server2": "Dosyalar hazırlanıyor...", + "gui_starting_server3": "Tor gizli hizmeti bekleniyor...", "gui_please_wait": "Lütfen bekleyin...", - "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor" + "error_hs_dir_cannot_create": "Gizli hizmet klasörü {0:s} oluşturulamıyor", + "error_hs_dir_not_writable": "Gizle hizmet klasörü {0:s} yazılabilir değil", + "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor", + "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" } -- cgit v1.2.3-54-g00ecf From 5442beba8b5759f6ee2a243f594642942036fdc4 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:00:30 +1000 Subject: Fix check_lacked_trans.py script to check subfolders and also match on more than first occurrence of strings._ in a single line --- install/check_lacked_trans.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 3313db7c..57568b1a 100644 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -54,7 +54,7 @@ def main(): dir = args.onionshare_dir - src = files_in(dir, 'onionshare') + files_in(dir, 'onionshare_gui') + src = files_in(dir, 'onionshare') + files_in(dir, 'onionshare_gui') + files_in(dir, 'onionshare_gui/share_mode') + files_in(dir, 'onionshare_gui/receive_mode') + files_in(dir, 'install/scripts') pysrc = [p for p in src if p.endswith('.py')] lang_code = args.lang_code @@ -64,11 +64,11 @@ def main(): for line in fileinput.input(pysrc, openhook=fileinput.hook_encoded('utf-8')): # search `strings._('translate_key')` # `strings._('translate_key', True)` - m = re.search(r'strings\._\((.*?)\)', line) + m = re.findall(r'strings\._\((.*?)\)', line) if m: - arg = m.group(1) - key = arg.split(',')[0].strip('''"' ''') - translate_keys.add(key) + for match in m: + key = match.split(',')[0].strip('''"' ''') + translate_keys.add(key) if args.show_all_keys: for k in sorted(translate_keys): -- cgit v1.2.3-54-g00ecf From 1d8a902407d24e5cd51aa914be1b5d61d29f4e82 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:13:04 +1000 Subject: Also include test dir for strings --- install/check_lacked_trans.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 57568b1a..027edab1 100644 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -54,7 +54,12 @@ def main(): dir = args.onionshare_dir - src = files_in(dir, 'onionshare') + files_in(dir, 'onionshare_gui') + files_in(dir, 'onionshare_gui/share_mode') + files_in(dir, 'onionshare_gui/receive_mode') + files_in(dir, 'install/scripts') + src = files_in(dir, 'onionshare') + \ + files_in(dir, 'onionshare_gui') + \ + files_in(dir, 'onionshare_gui/share_mode') + \ + files_in(dir, 'onionshare_gui/receive_mode') + \ + files_in(dir, 'install/scripts') + \ + files_in(dir, 'test') pysrc = [p for p in src if p.endswith('.py')] lang_code = args.lang_code -- cgit v1.2.3-54-g00ecf From 1a273e772b951dcb54e470fa52e7e9e8a7d5c857 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:13:23 +1000 Subject: remove obsolete strings --- share/locale/cs.json | 17 ----------------- share/locale/da.json | 18 +----------------- share/locale/de.json | 10 ---------- share/locale/en.json | 14 -------------- share/locale/eo.json | 17 ----------------- share/locale/es.json | 10 ---------- share/locale/fi.json | 16 ---------------- share/locale/fr.json | 9 --------- share/locale/it.json | 16 ---------------- share/locale/nl.json | 15 --------------- share/locale/no.json | 3 --- share/locale/pt.json | 3 --- share/locale/ru.json | 3 --- share/locale/tr.json | 16 ---------------- 14 files changed, 1 insertion(+), 166 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index aaa80d1b..40e48f87 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -2,22 +2,15 @@ "config_onion_service": "Nastavuji onion service na portu {0:d}.", "preparing_files": "Připravuji soubory ke sdílení.", "wait_for_hs": "Čekám na HS až bude připravena:", - "wait_for_hs_trying": "Zkouším...", - "wait_for_hs_nope": "Ještě nepřipraven.", - "wait_for_hs_yup": "Připraven!", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", - "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", "large_filesize": "Varování: Posílání velkých souborů může trvat hodiny", - "error_tails_invalid_port": "Nesprávná hodnota, port musí být celé číslo", - "error_tails_unknown_root": "Neznámá chyba s Tails root procesem", "help_local_only": "Nepoužívat Tor: jen pro vývoj", "help_stay_open": "Nechat běžet onion service po skončení stahování", - "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Zaznamenat chyby na disk", "help_filename": "Seznam souborů a složek ke sdílení", @@ -33,11 +26,7 @@ "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_starting_server1": "Spouštím Tor onion service...", - "gui_starting_server2": "Zpracovávám soubory...", "gui_please_wait": "Prosím čekejte...", - "error_hs_dir_cannot_create": "Nejde vytvořit složka onion service {0:s}", - "error_hs_dir_not_writable": "nejde zapisovat do složky onion service {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", @@ -50,9 +39,6 @@ "zip_progress_bar_format": "Zpracovávám soubory: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare vyžaduje nejméně Tor 0.2.7.1 a nejméně python3-stem 1.4.0.", - "gui_menu_file_menu": "&File", - "gui_menu_settings_action": "&Settings", - "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", @@ -63,10 +49,7 @@ "gui_settings_authenticate_label": "Autentizační možnosti Toru", "gui_settings_authenticate_no_auth_option": "Žádná autentizace ani cookie autentizace", "gui_settings_authenticate_password_option": "Heslo", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Heslo", - "gui_settings_cookie_label": "Cesta ke cookie", - "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Uložit", "gui_settings_button_cancel": "Zrušit", "settings_saved": "Nastavení uloženo do {}", diff --git a/share/locale/da.json b/share/locale/da.json index d36f7035..00539212 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -2,23 +2,17 @@ "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", "preparing_files": "Forbereder filer som skal deles.", "wait_for_hs": "Venter på at HS bliver klar:", - "wait_for_hs_trying": "Prøver...", - "wait_for_hs_nope": "Endnu ikke klar.", - "wait_for_hs_yup": "Klar!", "give_this_url": "Giv denne URL til personen du sender filen til:", "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", "not_a_file": "{0:s} er ikke en gyldig fil.", "not_a_readable_file": "{0:s} er ikke en læsbar fil.", "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", - "download_page_loaded": "Downloadside indlæst", "other_page_loaded": "URL indlæst", "close_on_timeout": "Lukker automatisk da timeout er nået", "closing_automatically": "Lukker automatisk da download er færdig", "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop", "large_filesize": "Advarsel: Det kan tage timer at sende store filer", - "error_tails_invalid_port": "Ugyldig værdi, port skal være et heltal", - "error_tails_unknown_root": "Ukendt fejl med Tails-rodproces", "systray_menu_exit": "Afslut", "systray_download_started_title": "OnionShare-download startet", "systray_download_started_message": "En bruger startede download af dine filer", @@ -29,7 +23,6 @@ "help_local_only": "Undlad at bruge tor: kun til udvikling", "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder", - "help_transparent_torification": "Mit system er gennemsigtigt torifiseret", "help_stealth": "Opret usynlig onion-tjeneste (avanceret)", "help_debug": "Log programfejl til stdout, og log webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", @@ -46,11 +39,7 @@ "gui_canceled": "Annulleret", "gui_copied_url": "Kopierede URL til udklipsholder", "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", - "gui_starting_server1": "Starter Tor onion-tjeneste...", - "gui_starting_server2": "Databehandler filer...", "gui_please_wait": "Vent venligst...", - "error_hs_dir_cannot_create": "Kan ikke oprette onion-tjenestens mappe {0:s}", - "error_hs_dir_not_writable": "onion-tjenestens mappe {0:s} er skrivebeskyttet", "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse", "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", @@ -87,9 +76,7 @@ "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", "gui_settings_authenticate_password_option": "Adgangskode", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Adgangskode", - "gui_settings_cookie_label": "Cookiesti", "gui_settings_tor_bridges": "Understøttelse af Tor-bro", "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter", @@ -100,7 +87,6 @@ "gui_settings_button_save": "Gem", "gui_settings_button_cancel": "Annuller", "gui_settings_button_help": "Hjælp", - "gui_settings_shutdown_timeout_choice": "Sæt timer til automatisk stop?", "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", @@ -112,7 +98,6 @@ "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.", "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", - "settings_error_bundled_tor_canceled": "Tor-processen lukkede inden den blev færdig med at oprette forbindelse.", "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}", "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", @@ -130,6 +115,5 @@ "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)", - "persistent_url_in_use": "Denne deling bruger en vedvarende URL" + "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)" } diff --git a/share/locale/de.json b/share/locale/de.json index 8e87b89b..6c0fa861 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,22 +1,12 @@ { - "connecting_ctrlport": "Verbinde zum Tor-Kontrollport um den versteckten Dienst auf Port {0:d} laufen zu lassen.", - "cant_connect_ctrlport": "Konnte keine Verbindung zum Tor-Kontrollport auf Port {0:s} aufbauen. Läuft Tor?", - "cant_connect_socksport": "Konnte keine Verbindung zum Tor SOCKS5 Server auf Port {0:s} herstellen. OnionShare setzt voraus dass Tor Browser im Hintergrund läuft. Wenn du noch ihn noch noch nicht hast kannst du ihn unter https://www.torproject.org/ herunterladen.", "preparing_files": "Dateien werden vorbereitet.", "wait_for_hs": "Warte auf HS:", - "wait_for_hs_trying": "Verbindungsversuch...", - "wait_for_hs_nope": "Noch nicht bereit.", - "wait_for_hs_yup": "Bereit!", "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", - "download_page_loaded": "Seite geladen", "other_page_loaded": "URL geladen", "closing_automatically": "Halte automatisch an, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", - "error_tails_invalid_port": "Ungültiger Wert, Port muss eine ganze Zahl sein", - "error_tails_unknown_root": "Unbekannter Fehler mit Tails root Prozess", - "help_tails_port": "Nur für Tails: Port um den Firewall zu öffnen, starte onion service", "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Fehler auf Festplatte schreiben", diff --git a/share/locale/en.json b/share/locale/en.json index b1d247d9..a96fe526 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -2,9 +2,6 @@ "config_onion_service": "Configuring onion service on port {0:d}.", "preparing_files": "Preparing files to share.", "wait_for_hs": "Waiting for HS to be ready:", - "wait_for_hs_trying": "Trying…", - "wait_for_hs_nope": "Not ready yet.", - "wait_for_hs_yup": "Ready!", "give_this_url": "Give this address to the person you're sending the file to:", "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", "give_this_url_receive": "Give this address to the people sending you files:", @@ -19,8 +16,6 @@ "closing_automatically": "Stopped because download finished", "timeout_download_still_running": "Waiting for download to complete", "large_filesize": "Warning: Sending large files could take hours", - "error_tails_invalid_port": "Invalid value, port must be a regular number", - "error_tails_unknown_root": "Unknown error with Tails root process", "systray_menu_exit": "Quit", "systray_download_started_title": "OnionShare Download Started", "systray_download_started_message": "A user started downloading your files", @@ -30,8 +25,6 @@ "systray_download_canceled_message": "The user canceled the download", "systray_upload_started_title": "OnionShare Upload Started", "systray_upload_started_message": "A user started uploading files to your computer", - "systray_upload_completed_title": "OnionShare Upload Finished", - "systray_upload_completed_message": "The user finished uploading files to your computer", "help_local_only": "Do not attempt to use Tor: For development only", "help_stay_open": "Keep onion service running after download has finished", "help_shutdown_timeout": "Shut down the onion service after N seconds", @@ -62,11 +55,7 @@ "gui_copied_url": "The OnionShare address has been copied to clipboard", "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", - "gui_starting_server1": "Starting Tor onion service…", - "gui_starting_server2": "Compressing files…", "gui_please_wait": "Starting… Click to cancel", - "error_hs_dir_cannot_create": "Cannot create onion service dir {0:s}", - "error_hs_dir_not_writable": "onion service dir {0:s} is not writable", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", @@ -105,9 +94,7 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Password", - "gui_settings_cookie_label": "Cookie path", "gui_settings_tor_bridges": "Tor Bridge support", "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", @@ -135,7 +122,6 @@ "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", - "settings_error_bundled_tor_canceled": "The Tor process closed before it could finish connecting.", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", diff --git a/share/locale/eo.json b/share/locale/eo.json index 0745ecaf..18e73165 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -2,22 +2,15 @@ "config_onion_service": "Agordas onion service je pordo {0:d}.", "preparing_files": "Preparas dosierojn por kundivido.", "wait_for_hs": "Atendas al hidden sevice por esti preta:", - "wait_for_hs_trying": "Provas...", - "wait_for_hs_nope": "Ankoraŭ ne preta.", - "wait_for_hs_yup": "Preta!", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", "not_a_file": "{0:s} ne estas dosiero.", - "download_page_loaded": "Download page loaded", "other_page_loaded": "URL loaded", "closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis", "large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn", - "error_tails_invalid_port": "Malĝusta valoro, pordo-numero devas esti plena numero", - "error_tails_unknown_root": "Nekonata eraro kun Tails-root-procezo", "help_local_only": "Ne strebu uzi tor: nur por evoluado", "help_stay_open": "Lasu onion service funkcii post fino de elŝuto", - "help_transparent_torification": "My system is transparently torified", "help_stealth": "Create stealth onion service (advanced)", "help_debug": "Protokoli erarojn sur disko", "help_filename": "Listo de dosieroj aŭ dosierujoj por kundividi", @@ -33,11 +26,7 @@ "gui_canceled": "Nuligita", "gui_copied_url": "URL kopiita en tondujon", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", - "gui_starting_server1": "Startigas Tor onion service...", - "gui_starting_server2": "Compressing files...", "gui_please_wait": "Bonvolu atendi...", - "error_hs_dir_cannot_create": "Ne eblas krei hidden-service-dosierujon {0:s}", - "error_hs_dir_not_writable": "ne eblas konservi dosierojn en hidden-service-dosierujo {0:s}", "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", @@ -50,9 +39,6 @@ "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", - "gui_menu_file_menu": "&File", - "gui_menu_settings_action": "&Settings", - "gui_menu_quit_action": "&Quit", "gui_settings_window_title": "Settings", "gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?", "gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser", @@ -63,10 +49,7 @@ "gui_settings_authenticate_label": "Tor authentication options", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Pasvorto", - "gui_settings_authenticate_cookie_option": "Kuketo", "gui_settings_password_label": "Pasvorto", - "gui_settings_cookie_label": "Cookie path", - "gui_settings_button_test": "Test Settings", "gui_settings_button_save": "Konservi", "gui_settings_button_cancel": "Nuligi", "settings_saved": "Agordoj konservitaj en {}", diff --git a/share/locale/es.json b/share/locale/es.json index 412fb501..b829540a 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,21 +1,11 @@ { - "connecting_ctrlport": "Conectando a puerto control de Tor para configurar servicio oculto en puerto {0:d}.", - "cant_connect_ctrlport": "No se pudo conectar a puerto control de Tor en puertos {0:s}. ¿Está funcionando Tor?", - "cant_connect_socksport": "No se pudo conectar al servidor SOCKS5 de Tor en el puerto {0:s}. ¿Está funcionando Tor?", "preparing_files": "Preparando los archivos para compartir.", "wait_for_hs": "Esperando a que HS esté listo:", - "wait_for_hs_trying": "Probando...", - "wait_for_hs_nope": "No está listo todavía.", - "wait_for_hs_yup": "Listo!", "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", - "download_page_loaded": "La página de descarga está lista.", "other_page_loaded": "La URL está lista.", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", - "error_tails_invalid_port": "Valor inválido, el puerto debe ser un entero", - "error_tails_unknown_root": "Error desconocido en el proceso de Tails ejecutando como roo", - "help_tails_port": "Sólo Tails: puerto para abrir en el firewall, al levantar el servicio oculto", "help_local_only": "No intentar usar Tor: sólo para desarrollo", "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado", "help_debug": "Guardar registro de errores en el disco", diff --git a/share/locale/fi.json b/share/locale/fi.json index 00768528..09186be8 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -1,25 +1,14 @@ { - "connecting_ctrlport": "Yhdistetään Torin ohjausporttiin että saadaan salattu palvelin porttiin {0:d}.", - "cant_connect_ctrlport": "Ei voi yhdistää Torin ohjausporttiin portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", - "cant_connect_socksport": "Ei voi yhdistää Tor SOCKS5 palveluun portissa {0:s}. OnionShare tarvitsee Tor Browserin toimimaan taustalla. Jos sinulla ei ole sitä niin voit hakea sen osoitteesta https://www.torproject.org/.", "preparing_files": "Valmistellaan tiedostoja jaettavaksi.", "wait_for_hs": "Odotetaan piilopalvelun valmistumista:", - "wait_for_hs_trying": "Yritetään...", - "wait_for_hs_nope": "Ei vielä valmis.", - "wait_for_hs_yup": "Valmis!", "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:", "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", "not_a_file": "{0:s} Ei ole tiedosto.", - "download_page_loaded": "Lataussivu ladattu", "other_page_loaded": "URL-osoite ladattu", "closing_automatically": "Lataus valmis. Suljetaan automaattisesti", "large_filesize": "Varoitus: Isojen tiedostojen lähetys saattaa kestää tunteja", - "error_tails_invalid_port": "Väärä arvo, portti pitää olla koknaisluku", - "error_tails_unknown_root": "Tuntematon virhe Tailsissa", - "help_tails_port": "Vain Tails: portti palomuurin läpi, käynnistetään salainen palvelin", "help_local_only": "Älä käytä Toria: vain ohjelmakehitykseen", "help_stay_open": "Pidä piilopalvelu käynnissä latauksen jälkeen.", - "help_transparent_torification": "Järjestelmäni käyttää Toria läpinäkyvästi", "help_debug": "Tallentaa virheet levylle", "help_filename": "Luettele jaettavat tiedostot tai kansiot", "gui_drag_and_drop": "Vedä ja pudota\ntiedostot tänne", @@ -32,12 +21,7 @@ "gui_downloads": "Lataukset:", "gui_canceled": "Peruutettu", "gui_copied_url": "URL-osoite kopioitu leikepöydälle", - "gui_starting_server1": "Käynnistetään Tor piilopalvelu...", - "gui_starting_server2": "Tiivistän tiedostoja...", - "gui_starting_server3": "Odotetaan Tor piilopalvelua...", "gui_please_wait": "Odota...", - "error_hs_dir_cannot_create": "Piilopalvelulle ei pystytty luomaan hakemistoa {0:s}", - "error_hs_dir_not_writable": "Piilopalvelun hakemistoon {0:s} ei voi kirjoittaa", "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua", "zip_progress_bar_format": "Tiivistän tiedostoja: %p%" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 6ec20b3b..b6f6eaa7 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,26 +1,17 @@ { - "connecting_ctrlport": "Connexion au réseau Tor pour mettre en place un onion service sur le port {0:d}.", - "cant_connect_ctrlport": "Impossible de se connecter au port de contrôle Tor sur le port {0:s}. Est-ce que Tor tourne ?", "preparing_files": "Préparation des fichiers à partager.", "wait_for_hs": "En attente du HS:", - "wait_for_hs_trying": "Tentative...", - "wait_for_hs_nope": "Pas encore prêt.", - "wait_for_hs_yup": "Prêt !", "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", "ctrlc_to_stop": "Ctrl-C arrête le serveur", "not_a_file": "{0:s} n'est pas un fichier.", - "download_page_loaded": "Page de téléchargement chargée", "other_page_loaded": "URL chargée", "closing_automatically": "Fermeture automatique car le téléchargement est fini", - "error_tails_invalid_port": "Valeur invalide, le port doit être un nombre entier", - "error_tails_unknown_root": "Erreur inconnue avec un processus root sur Tails", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare Démarré", "systray_download_started_message": "Un utilisateur télécharge vos fichiers", "systray_download_completed_title": "Téléchargement OnionShare Complete", "systray_download_canceled_title": "Téléchargement OnionShare Annulé", "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", - "help_tails_port": "Seulement sur Tails: port pour ouvrir le firewall, démarrage du onion service", "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", "help_debug": "Enregistrer les erreurs sur le disque", diff --git a/share/locale/it.json b/share/locale/it.json index 7ad38169..304e0cb9 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,25 +1,14 @@ { - "connecting_ctrlport": "Connessione alla porta di controllo di Tor per inizializzare il servizio nascosto sulla porta {0:d}.", - "cant_connect_ctrlport": "Impossibile connettere alla porta di controllo di Tor sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", - "cant_connect_socksport": "Impossibile connettersi al server Tor SOCKS5 sulla porta {0:s}. OnionShare richiede l'esecuzione in background di Tor Browser per funzionare. Se non è installato puoi scaricarlo da https://www.torproject.org/.", "preparing_files": "Preparazione dei files da condividere.", "wait_for_hs": "In attesa che l'HS sia pronto:", - "wait_for_hs_trying": "Tentativo...", - "wait_for_hs_nope": "Non è ancora pronto.", - "wait_for_hs_yup": "Pronto!", "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", - "download_page_loaded": "Pagina di Download caricata", "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica dopo aver finito il download", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "error_tails_invalid_port": "Valore non valido, la porta deve essere un numero intero", - "error_tails_unknown_root": "Errore sconosciuto con il processo Tails root", - "help_tails_port": "Solo per Tails: porta per passare il firewall, avvio del servizio nascosto", "help_local_only": "Non usare tor: è solo per lo sviluppo", "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", - "help_transparent_torification": "Il mio sistema usa tor in modo trasparente", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", @@ -32,12 +21,7 @@ "gui_downloads": "Downloads:", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", - "gui_starting_server1": "Avviamento del servizio nascosto Tor...", - "gui_starting_server2": "Elaborazione files...", - "gui_starting_server3": "In attesa del servizio nascosto Tor...", "gui_please_wait": "Attendere prego...", - "error_hs_dir_cannot_create": "Impossibile create la cartella per il servizio nascosto {0:s}", - "error_hs_dir_not_writable": "La cartella per il servizio nascosto {0:s} non ha i permessi di scrittura", "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione", "zip_progress_bar_format": "Elaborazione files: %p%" } diff --git a/share/locale/nl.json b/share/locale/nl.json index 4031effd..67297ae0 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -2,23 +2,17 @@ "config_onion_service": "Onion service configureren op poort {0:d}.", "preparing_files": "Bestanden om te delen aan het voorbereiden.", "wait_for_hs": "Wachten op gereed zijn van HS:", - "wait_for_hs_trying": "Proberen...", - "wait_for_hs_nope": "Nog niet gereed.", - "wait_for_hs_yup": "Gereed!", "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", "not_a_file": "{0:s} is geen bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", - "download_page_loaded": "Downloadpagina geladen", "other_page_loaded": "URL geladen", "close_on_timeout": "Sluit automatisch omdat timeout bereikt is", "closing_automatically": "Sluit automatisch omdat download gereed is", "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten", "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren", - "error_tails_invalid_port": "Ongeldige waarde, poort moet een integer zijn", - "error_tails_unknown_root": "Onbekende fout met het Tails root proces", "systray_menu_exit": "Afsluiten", "systray_download_started_title": "OnionShare download gestart", "systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden", @@ -29,7 +23,6 @@ "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling", "help_stay_open": "Laat verborgen service draaien nadat download gereed is", "help_shutdown_timeout": "Sluit de Onion service na N seconden", - "help_transparent_torification": "Mijn systeem gebruikt Tor als proxyserver", "help_stealth": "Maak stealth Onion service (geavanceerd)", "help_debug": "Log fouten naar harde schijf", "help_filename": "Lijst van bestanden of mappen om te delen", @@ -44,11 +37,7 @@ "gui_canceled": "Afgebroken", "gui_copied_url": "URL gekopieerd naar klembord", "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", - "gui_starting_server1": "Tor onion service wordt gestart...", - "gui_starting_server2": "Bestanden verwerken...", "gui_please_wait": "Moment geduld...", - "error_hs_dir_cannot_create": "Kan verborgen service map {0:s} niet aanmaken", - "error_hs_dir_not_writable": "Verborgen service map {0:s} is niet schrijfbaar", "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie", "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", @@ -84,13 +73,10 @@ "gui_settings_authenticate_label": "Tor authenticatie opties", "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", - "gui_settings_authenticate_cookie_option": "Cookie", "gui_settings_password_label": "Wachtwoord", - "gui_settings_cookie_label": "Cookie pad", "gui_settings_button_save": "Opslaan", "gui_settings_button_cancel": "Annuleren", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout_choice": "Auto-stop timer instellen?", "gui_settings_shutdown_timeout": "Stop delen om:", "settings_saved": "Instellingen opgeslagen in {}", "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.", @@ -102,7 +88,6 @@ "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.", "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.", - "settings_error_bundled_tor_canceled": "Het Tor is afgesloten voordat het kon verbinden.", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}", "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.", diff --git a/share/locale/no.json b/share/locale/no.json index 8b131038..a04710d2 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,10 +1,7 @@ { - "connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0:d}.", - "cant_connect_ctrlport": "Klarte ikke å koble til Tors kontroll-porter {0:s}. Sjekk at Tor kjører.", "give_this_url": "Gi personen du vil sende filen til denne URL-en:", "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "Kopierte URL-en til utklippstavlen", - "download_page_loaded": "Nedlastingsside lastet", "other_page_loaded": "En annen side har blitt lastet" } diff --git a/share/locale/pt.json b/share/locale/pt.json index 71391957..1b2d3139 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,10 +1,7 @@ { - "connecting_ctrlport": "Conectando-se à porta de controle Tor para configurar serviço escondido na porta {0:d}.", - "cant_connect_ctrlport": "Não pode conectar à porta de controle Tor na porta {0:s}. O Tor está rodando?", "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", "not_a_file": "{0:s} não é um arquivo.", "gui_copied_url": "URL foi copiado para área de transferência", - "download_page_loaded": "Página de download carregada", "other_page_loaded": "Outra página tem sido carregada" } diff --git a/share/locale/ru.json b/share/locale/ru.json index 193c158d..b7c89d69 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,11 +1,8 @@ { - "connecting_ctrlport": "Соединяемся с контрольным портом Tor для создания скрытого сервиса на порту {0:d}.", - "cant_connect_ctrlport": "Невозможно соединиться с контрольным портом Tor на порту {0:s}. Tor запущен?", "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", "not_a_file": "{0:s} не является файлом.", "gui_copied_url": "Ссылка скопирована в буфер обмена", - "download_page_loaded": "Страница закачки загружена", "other_page_loaded": "Другая страница была загружена", "gui_copy_url": "Скопировать ссылку" } diff --git a/share/locale/tr.json b/share/locale/tr.json index d8097909..7b531bd6 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,25 +1,14 @@ { - "connecting_ctrlport": "{0:d} portundaki gizli hizmet(GH) kurulumu için Tor kontrol portuna bağlanıyor.", - "cant_connect_ctrlport": "Tor kontrol {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", - "cant_connect_socksport": "Tor SOCKS5 sunucu {0:s} portuna bağlanamıyor. OnionShare çalışması için arkaplanda Tor Browser çalışması gerekiyor. Tor Browser indirmediyseniz, https://www.torproject.org/", "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", "wait_for_hs": "GH hazır olması bekleniyor:", - "wait_for_hs_trying": "Deneniyor...", - "wait_for_hs_nope": "Henüz hazır değil.", - "wait_for_hs_yup": "Hazır!", "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", - "download_page_loaded": "İndirme sayfası yüklendi", "other_page_loaded": "Diğer sayfa yüklendi", "closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor", "large_filesize": "Uyarı: Büyük dosyaların gönderimi saatler sürebilir", - "error_tails_invalid_port": "Geçersiz değer, port sayı olmalıdır", - "error_tails_unknown_root": "Tails ana işlemi ile ilgili bilinmeyen hata", - "help_tails_port": "Sadece Tails: port for opening firewall, starting onion service", "help_local_only": "Tor kullanmaya kalkışmayın: sadece geliştirme için", "help_stay_open": "İndirme tamamlandıktan sonra gizli hizmeti çalıştırmaya devam et", - "help_transparent_torification": "Sistemim apaçık torlu", "help_debug": "Hata kayıtlarını diske kaydet", "help_filename": "Paylaşmak için dosya ve klasörler listesi", "gui_drag_and_drop": "Dosyaları buraya\n Sürükle ve Bırak", @@ -32,12 +21,7 @@ "gui_downloads": "İndirilenler:", "gui_canceled": "İptal edilen", "gui_copied_url": "Panoya kopyalanan URL", - "gui_starting_server1": "Tor gizli hizmeti başlatılıyor...", - "gui_starting_server2": "Dosyalar hazırlanıyor...", - "gui_starting_server3": "Tor gizli hizmeti bekleniyor...", "gui_please_wait": "Lütfen bekleyin...", - "error_hs_dir_cannot_create": "Gizli hizmet klasörü {0:s} oluşturulamıyor", - "error_hs_dir_not_writable": "Gizle hizmet klasörü {0:s} yazılabilir değil", "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor", "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" } -- cgit v1.2.3-54-g00ecf From 1b4c989f067e3db31a85571d5f20b65334bb6272 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 15 Aug 2018 09:09:52 +1000 Subject: Add explicit dependency on rpm-build in the BUILD.md. Add a link to community-provided OpenSuSE instructions --- BUILD.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 77d5ee0e..4b87b98f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,7 +13,7 @@ Install the needed dependencies: For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy` -For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4` +For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 rpm-build` After that you can try both the CLI and the GUI version of OnionShare: @@ -28,6 +28,8 @@ Create a .deb on Debian-like distros: `./install/build_deb.sh` Create a .rpm on Fedora-like distros: `./install/build_rpm.sh` +For OpenSuSE: There are instructions for building [in the wiki](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support#opensuse-leap-150). + For ArchLinux: There is a PKBUILD available [here](https://aur.archlinux.org/packages/onionshare/) that can be used to install OnionShare. If you find that these instructions don't work for your Linux distribution or version, consult the [Linux Distribution Support wiki guide](https://github.com/micahflee/onionshare/wiki/Linux-Distribution-Support), which might contain extra instructions. -- cgit v1.2.3-54-g00ecf From 2de9359629d07a57cf7a439f485907a0cc0d2560 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 21 Aug 2018 19:31:02 +1000 Subject: Introduce v3 onion support --- .travis.yml | 2 +- BUILD.md | 4 +- install/requirements-windows.txt | 4 ++ install/requirements.txt | 4 ++ onionshare/onion.py | 51 ++++++++++++--- onionshare/onionkey.py | 126 ++++++++++++++++++++++++++++++++++++++ onionshare_gui/server_status.py | 11 +++- onionshare_gui/settings_dialog.py | 9 ++- 8 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 onionshare/onionkey.py diff --git a/.travis.yml b/.travis.yml index 9010e77a..301f87a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ python: - "nightly" # command to install dependencies install: - - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 + - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 pycrypto pynacl cryptography pysha3 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics diff --git a/BUILD.md b/BUILD.md index e6e54951..57c83438 100644 --- a/BUILD.md +++ b/BUILD.md @@ -11,9 +11,9 @@ cd onionshare Install the needed dependencies: -For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy` +For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy python3-cryptography python3-crypto python3-nacl python3-pip; pip3 install pysha3` -For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4` +For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 python3-pynacl python3-cryptography python3-crypto python3-pip; pip3 install pysha3` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 32b8da4a..611edf3c 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,4 +1,5 @@ click==6.7 +cryptography==2.1.4 Flask==0.12.2 future==0.16.0 itsdangerous==0.24 @@ -8,6 +9,9 @@ pefile==2017.11.5 PyInstaller==3.3.1 PyQt5==5.9.2 PySocks==1.6.7 +pynacl==1.2.1 +pycrypto==2.6.1 +pysha3==1.0.2 sip==4.19.6 stem==1.6.0 Werkzeug==0.14.1 diff --git a/install/requirements.txt b/install/requirements.txt index c7828080..964030e8 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,4 +1,5 @@ click==6.7 +cryptography==2.1.4 Flask==0.12.2 itsdangerous==0.24 Jinja2==2.10 @@ -6,6 +7,9 @@ MarkupSafe==1.0 PyInstaller==3.3.1 PyQt5==5.9.2 PySocks==1.6.7 +pycrypto==2.6.1 +pynacl==1.2.1 +pysha3==1.0.2 sip==4.19.6 stem==1.6.0 Werkzeug==0.14.1 diff --git a/onionshare/onion.py b/onionshare/onion.py index 4812842a..73c94600 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -21,8 +21,10 @@ along with this program. If not, see . from stem.control import Controller from stem import ProtocolError, SocketClosed from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure -import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex +import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex +from distutils.version import LooseVersion as Version +from . import onionkey from . import socks from . import common, strings from .settings import Settings @@ -444,20 +446,49 @@ class Onion(object): basic_auth = None if self.settings.get('private_key'): - key_type = "RSA1024" - key_content = self.settings.get('private_key') - self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a saved private key') + try: + # is the key a v2 key? + key = onionkey.is_v2_key(self.settings.get('private_key')) + key_type = "RSA1024" + key_content = self.settings.get('private_key') + # The below section is commented out because re-publishing + # a pre-prepared v3 private key is currently unstable in Tor. + # This is fixed upstream but won't reach stable until 0.3.5 + # (expected in December 2018) + # See https://trac.torproject.org/projects/tor/ticket/25552 + # Until then, we will deliberately not work with 'persistent' + # v3 onions, which should not be possible via the GUI settings + # anyway. + # Our ticket: https://github.com/micahflee/onionshare/issues/677 + except: + pass + # Assume it was a v3 key + # key_type = "ED25519-V3" + # key_content = self.settings.get('private_key') + self.common.log('Onion', 'Starting a hidden service with a saved private key') else: - key_type = "NEW" - key_content = "RSA1024" - self.common.log('Onion', 'start_onion_service', 'Starting a hidden service with a new private key') + # Work out if we can support v3 onion services, which are preferred + if Version(self.tor_version) >= Version('0.3.3'): + key_type = "ED25519-V3" + key_content = onionkey.generate_v3_private_key()[0] + else: + # fall back to v2 onion services + key_type = "RSA1024" + key_content = onionkey.generate_v2_private_key()[0] + self.common.log('Onion', 'Starting a hidden service with a new private key') + + # v3 onions don't yet support basic auth. Our ticket: + # https://github.com/micahflee/onionshare/issues/697 + if key_type == "ED25519-V3": + basic_auth = None + self.stealth = False try: if basic_auth != None: - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type = key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type=key_type, key_content=key_content) else: # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type = key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type=key_type, key_content=key_content) except ProtocolError: raise TorErrorProtocolError(strings._('error_tor_protocol_error')) @@ -468,7 +499,7 @@ class Onion(object): # A new private key was generated and is in the Control port response. if self.settings.get('save_private_key'): if not self.settings.get('private_key'): - self.settings.set('private_key', res.private_key) + self.settings.set('private_key', key_content) if self.stealth: # Similar to the PrivateKey, the Control port only returns the ClientAuth diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py new file mode 100644 index 00000000..89c781ab --- /dev/null +++ b/onionshare/onionkey.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2017 Micah Lee + +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 . +""" + +import os +import sys + +import base64 +import hashlib +# Need sha3 if python version is older than 3.6, otherwise +# we can't use hashlib.sha3_256 +if sys.version_info < (3, 6): + import sha3 + +import nacl.signing + +from Crypto.PublicKey import RSA + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa + + +b = 256 + +def bit(h, i): + return (h[i // 8] >> (i % 8)) & 1 + + +def encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) + + +def H(m): + return hashlib.sha512(m).digest() + + +def expandSK(sk): + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) + assert len(k) == 32 + return encodeint(a) + k + + +def onion_url_from_private_key(private_key): + """ + Derives the public key (.onion hostname) from a v3-style + Onion private key. + """ + private_key = nacl.signing.SigningKey(seed=private_key) + pubkey = bytes(private_key.verify_key) + version = b'\x03' + checksum = hashlib.sha3_256(b".onion checksum" + pubkey + version).digest()[:2] + onion_address = "http://{}.onion".format(base64.b32encode(pubkey + checksum + version).decode().lower()) + return onion_address + + +def generate_v3_private_key(): + """ + Generates a private and public key for use with v3 style Onions. + Returns both the private key as well as the public key (.onion hostname) + """ + secretKey = os.urandom(32) + expandedSecretKey = expandSK(secretKey) + private_key = base64.b64encode(expandedSecretKey).decode() + return (private_key, onion_url_from_private_key(secretKey)) + +def generate_v2_private_key(): + """ + Generates a private and public key for use with v2 style Onions. + Returns both the serialized private key (compatible with Stem) + as well as the public key (.onion hostname) + """ + # Generate v2 Onion Service private key + private_key = rsa.generate_private_key(public_exponent=65537, + key_size=1024, + backend=default_backend()) + hs_public_key = private_key.public_key() + + # Pre-generate the public key (.onion hostname) + der_format = hs_public_key.public_bytes(encoding=serialization.Encoding.DER, + format=serialization.PublicFormat.PKCS1) + + onion_url = base64.b32encode(hashlib.sha1(der_format).digest()[:-10]).lower().decode() + + # Generate Stem-compatible key content + pem_format = private_key.private_bytes(encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()) + serialized_key = ''.join(pem_format.decode().split('\n')[1:-2]) + + return (serialized_key, onion_url) + +def is_v2_key(key): + """ + Helper function for determining if a key is RSA1024 (v2) or not. + """ + try: + # Import the key + key = RSA.importKey(base64.b64decode(key)) + # Is this a v2 Onion key? (1024 bits) If so, we should keep using it. + if key.n.bit_length() == 1024: + return True + else: + return False + except: + return False + diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 1562ee10..46310605 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import platform +import textwrap from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -88,7 +89,7 @@ class ServerStatus(QtWidgets.QWidget): self.url = QtWidgets.QLabel() self.url.setFont(url_font) self.url.setWordWrap(True) - self.url.setMinimumHeight(60) + self.url.setMinimumHeight(65) self.url.setMinimumSize(self.url.sizeHint()) self.url.setStyleSheet(self.common.css['server_status_url']) @@ -162,7 +163,13 @@ class ServerStatus(QtWidgets.QWidget): else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - self.url.setText(self.get_url()) + # Wrap the Onion URL if it's a big v3 one + url_length=len(self.get_url()) + if url_length > 60: + wrapped_onion_url = textwrap.fill(self.get_url(), 50) + self.url.setText(wrapped_onion_url) + else: + self.url.setText(self.get_url()) self.url.show() self.copy_url_button.show() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 94480205..a41226f6 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -19,6 +19,7 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui import sys, platform, datetime, re +from distutils.version import LooseVersion as Version from onionshare import strings, common from onionshare.settings import Settings @@ -64,7 +65,7 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - # Whether or not to save the Onion private key for reuse + # Whether or not to save the Onion private key for reuse (persistent URLs) self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox", True)) @@ -421,6 +422,9 @@ class SettingsDialog(QtWidgets.QDialog): self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + # Using persistent URLs with v3 onions is not yet stable + if Version(self.onion.tor_version) >= Version('0.3.2.9'): + self.save_private_key_checkbox.hide() downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -445,6 +449,9 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + # Using Client Auth with v3 onions is not yet possible + if Version(self.onion.tor_version) >= Version('0.3.2.9'): + stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: -- cgit v1.2.3-54-g00ecf From 5c8b0d77965caba786677098bc01b4aab152e480 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:45:08 +1000 Subject: Rather than hide persistence/stealth mode altogether if the Tor version is high enough for v3, give the user the option to 'use legacy v2 onions' in Settings dialog, so that they may continue to use persistence etc --- onionshare/onion.py | 4 +- onionshare/settings.py | 1 + onionshare_gui/settings_dialog.py | 88 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 73c94600..18ebeb5d 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -468,7 +468,7 @@ class Onion(object): self.common.log('Onion', 'Starting a hidden service with a saved private key') else: # Work out if we can support v3 onion services, which are preferred - if Version(self.tor_version) >= Version('0.3.3'): + if Version(self.tor_version) >= Version('0.3.2.9') and not self.settings.get('use_legacy_v2_onions'): key_type = "ED25519-V3" key_content = onionkey.generate_v3_private_key()[0] else: @@ -479,7 +479,7 @@ class Onion(object): # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 - if key_type == "ED25519-V3": + if key_type == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): basic_auth = None self.stealth = False diff --git a/onionshare/settings.py b/onionshare/settings.py index 6d551ca0..9968a828 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -68,6 +68,7 @@ class Settings(object): 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', + 'use_legacy_v2_onions': False, 'save_private_key': False, 'private_key': '', 'slug': '', diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index a41226f6..8574bc6e 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -65,15 +65,25 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + # Whether or not to use legacy v2 onions + self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() + 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.clicked.connect(self.use_legacy_v2_onions_clicked) + if Version(self.onion.tor_version) < Version('0.3.2.9'): + self.use_legacy_v2_onions_checkbox.hide() + # Whether or not to save the Onion private key for reuse (persistent URLs) self.save_private_key_checkbox = QtWidgets.QCheckBox() 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.clicked.connect(self.save_private_key_checkbox_clicked) # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) + sharing_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) sharing_group_layout.addWidget(self.save_private_key_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) @@ -118,6 +128,7 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) + self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -134,8 +145,8 @@ class SettingsDialog(QtWidgets.QDialog): stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(hidservauth_details) stealth_group_layout.addWidget(self.hidservauth_copy_button) - stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) - stealth_group.setLayout(stealth_group_layout) + self.stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) + self.stealth_group.setLayout(stealth_group_layout) # Automatic updates options @@ -380,7 +391,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(stealth_group) + left_col_layout.addWidget(self.stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() @@ -417,15 +428,25 @@ class SettingsDialog(QtWidgets.QDialog): else: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) + use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) + # Legacy v2 mode is forced on if persistence is enabled + self.use_legacy_v2_onions_checkbox.setEnabled(False) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.use_legacy_v2_onions_checkbox.setEnabled(True) # Using persistent URLs with v3 onions is not yet stable - if Version(self.onion.tor_version) >= Version('0.3.2.9'): + if Version(self.onion.tor_version) >= Version('0.3.2.9') and not use_legacy_v2_onions: self.save_private_key_checkbox.hide() + if use_legacy_v2_onions or save_private_key: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.save_private_key_checkbox.show() + self.stealth_group.show() + downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -444,14 +465,17 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth = self.old_settings.get('use_stealth') if use_stealth: self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) + # Legacy v2 mode is forced on if Stealth is enabled + self.use_legacy_v2_onions_checkbox.setEnabled(False) if save_private_key: hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.use_legacy_v2_onions_checkbox.setEnabled(True) # Using Client Auth with v3 onions is not yet possible - if Version(self.onion.tor_version) >= Version('0.3.2.9'): - stealth_group.hide() + if not use_legacy_v2_onions: + self.stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: @@ -627,6 +651,37 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) + def use_legacy_v2_onions_clicked(self, checked): + """ + Show the persistent and stealth options since we're using legacy onions. + """ + if checked: + self.save_private_key_checkbox.show() + self.stealth_group.show() + else: + self.save_private_key_checkbox.hide() + self.stealth_group.hide() + + def save_private_key_checkbox_clicked(self, checked): + """ + Prevent the v2 legacy mode being switched off if persistence is enabled + """ + if checked: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_legacy_v2_onions_checkbox.setEnabled(False) + else: + self.use_legacy_v2_onions_checkbox.setEnabled(True) + + def stealth_checkbox_clicked_connect(self, checked): + """ + Prevent the v2 legacy mode being switched off if stealth is enabled + """ + if checked: + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_legacy_v2_onions_checkbox.setEnabled(False) + else: + self.use_legacy_v2_onions_checkbox.setEnabled(True) + def downloads_button_clicked(self): """ Browse for a new downloads directory @@ -813,7 +868,16 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('close_after_first_download', self.close_after_first_download_checkbox.isChecked()) settings.set('shutdown_timeout', self.shutdown_timeout_checkbox.isChecked()) + + # Complicated logic here to force v2 onion mode on or off depending on other settings + if self.use_legacy_v2_onions_checkbox.isChecked(): + use_legacy_v2_onions = True + else: + use_legacy_v2_onions = False + if self.save_private_key_checkbox.isChecked(): + # force the legacy mode on + use_legacy_v2_onions = True settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) @@ -824,6 +888,18 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('slug', '') # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') + + if use_legacy_v2_onions: + settings.set('use_legacy_v2_onions', True) + else: + settings.set('use_legacy_v2_onions', False) + # If we are not using legacy mode, but we previously had persistence turned on, force it off! + settings.set('save_private_key', False) + settings.set('private_key', '') + settings.set('slug', '') + # Also unset the HidServAuth if we are removing our reusable private key + settings.set('hidservauth_string', '') + settings.set('downloads_dir', self.downloads_dir_lineedit.text()) settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) settings.set('receive_public_mode', self.receive_public_mode_checkbox.isChecked()) -- cgit v1.2.3-54-g00ecf From 16430f5fad1df7747848154d9e3dbf8e6e4a5249 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:50:16 +1000 Subject: Fix tests --- test/test_onionshare_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 3942ab8c..19851db5 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -60,6 +60,7 @@ class TestSettings: 'tor_bridges_use_meek_lite_amazon': False, 'tor_bridges_use_meek_lite_azure': False, 'tor_bridges_use_custom_bridges': '', + 'use_legacy_v2_onions': False, 'save_private_key': False, 'private_key': '', 'slug': '', -- cgit v1.2.3-54-g00ecf From f0efb10d08a51c8176773ea7bb432cbaa36de3ec Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 17:03:15 +1000 Subject: Add missing locale key for legacy v2 onions --- share/locale/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/share/locale/en.json b/share/locale/en.json index b1d247d9..8205a29a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -153,6 +153,7 @@ "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", -- cgit v1.2.3-54-g00ecf From 47fc55aac1d200ca9624d60ac91af20e3d120216 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 23 Aug 2018 11:02:28 +1000 Subject: Don't try and parse Tor version in order whether or not to show v2-only features. Just note in the QLabel what is v2-only. Still force v2 legacy mode on when using persistence or stealth. --- onionshare_gui/settings_dialog.py | 35 +++++++---------------------------- share/locale/en.json | 4 ++-- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 8574bc6e..b2515a69 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -19,7 +19,6 @@ along with this program. If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui import sys, platform, datetime, re -from distutils.version import LooseVersion as Version from onionshare import strings, common from onionshare.settings import Settings @@ -69,9 +68,6 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() 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.clicked.connect(self.use_legacy_v2_onions_clicked) - if Version(self.onion.tor_version) < Version('0.3.2.9'): - self.use_legacy_v2_onions_checkbox.hide() # Whether or not to save the Onion private key for reuse (persistent URLs) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -145,8 +141,8 @@ class SettingsDialog(QtWidgets.QDialog): stealth_group_layout.addWidget(self.stealth_checkbox) stealth_group_layout.addWidget(hidservauth_details) stealth_group_layout.addWidget(self.hidservauth_copy_button) - self.stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) - self.stealth_group.setLayout(stealth_group_layout) + stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) + stealth_group.setLayout(stealth_group_layout) # Automatic updates options @@ -391,7 +387,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(self.stealth_group) + left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() @@ -438,14 +434,9 @@ class SettingsDialog(QtWidgets.QDialog): else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setEnabled(True) - # Using persistent URLs with v3 onions is not yet stable - if Version(self.onion.tor_version) >= Version('0.3.2.9') and not use_legacy_v2_onions: - self.save_private_key_checkbox.hide() if use_legacy_v2_onions or save_private_key: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) - self.save_private_key_checkbox.show() - self.stealth_group.show() downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) @@ -473,9 +464,6 @@ class SettingsDialog(QtWidgets.QDialog): else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.use_legacy_v2_onions_checkbox.setEnabled(True) - # Using Client Auth with v3 onions is not yet possible - if not use_legacy_v2_onions: - self.stealth_group.hide() use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: @@ -651,17 +639,6 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) - def use_legacy_v2_onions_clicked(self, checked): - """ - Show the persistent and stealth options since we're using legacy onions. - """ - if checked: - self.save_private_key_checkbox.show() - self.stealth_group.show() - else: - self.save_private_key_checkbox.hide() - self.stealth_group.hide() - def save_private_key_checkbox_clicked(self, checked): """ Prevent the v2 legacy mode being switched off if persistence is enabled @@ -670,7 +647,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_checkbox.setEnabled(False) else: - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not self.stealth_checkbox.isChecked(): + self.use_legacy_v2_onions_checkbox.setEnabled(True) def stealth_checkbox_clicked_connect(self, checked): """ @@ -680,7 +658,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_checkbox.setEnabled(False) else: - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not self.save_private_key_checkbox.isChecked(): + self.use_legacy_v2_onions_checkbox.setEnabled(True) def downloads_button_clicked(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index 8205a29a..d73fb369 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -84,7 +84,7 @@ "gui_settings_window_title": "Settings", "gui_settings_stealth_label": "Stealth (advanced)", "gui_settings_stealth_option": "Create stealth onion services", - "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.
    More information.", + "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.

    NOTE: currently this only works with v2 onions.
    More information.", "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.", "gui_settings_autoupdate_label": "Check for upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -154,7 +154,7 @@ "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", - "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved addresses)", + "gui_save_private_key_checkbox": "Use a persistent (v2 only) address\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", -- cgit v1.2.3-54-g00ecf From 7879697ec6966deabf732b841afee9e0a0bf681f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 28 Aug 2018 09:33:49 +1000 Subject: Only wrap the v3 onion if the window is too small to show it unwrapped --- onionshare_gui/server_status.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 46310605..c0409749 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -57,6 +57,8 @@ class ServerStatus(QtWidgets.QWidget): self.web = None + self.resizeEvent(None) + # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() @@ -129,6 +131,24 @@ class ServerStatus(QtWidgets.QWidget): self.update() + def resizeEvent(self, event): + """ + When the widget is resized, try and adjust the display of a v3 onion URL. + """ + try: + self.get_url() + url_length=len(self.get_url()) + if url_length > 60: + width = self.frameGeometry().width() + if width < 530: + wrapped_onion_url = textwrap.fill(self.get_url(), 50) + self.url.setText(wrapped_onion_url) + else: + self.url.setText(self.get_url()) + except: + pass + + def shutdown_timeout_reset(self): """ Reset the timeout in the UI after stopping a share @@ -163,13 +183,7 @@ class ServerStatus(QtWidgets.QWidget): else: self.url_description.setToolTip(strings._('gui_url_label_stay_open', True)) - # Wrap the Onion URL if it's a big v3 one - url_length=len(self.get_url()) - if url_length > 60: - wrapped_onion_url = textwrap.fill(self.get_url(), 50) - self.url.setText(wrapped_onion_url) - else: - self.url.setText(self.get_url()) + self.url.setText(self.get_url()) self.url.show() self.copy_url_button.show() -- cgit v1.2.3-54-g00ecf From 2fd2cf3e2e686c22e203990d6ed8aa73aef2520f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 30 Aug 2018 15:18:29 +1000 Subject: Fix the passing of the latest version returned to the UI if it is an invalid version --- onionshare_gui/settings_dialog.py | 4 ++-- onionshare_gui/update_checker.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index e9d3ed8f..ab0b0b4d 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -658,8 +658,8 @@ class SettingsDialog(QtWidgets.QDialog): Alert(self.common, strings._('update_error_check_error', True), QtWidgets.QMessageBox.Warning) close_forced_update_thread() - def update_invalid_version(): - Alert(self.common, strings._('update_error_invalid_latest_version', True).format(e.latest_version), QtWidgets.QMessageBox.Warning) + def update_invalid_version(latest_version): + Alert(self.common, strings._('update_error_invalid_latest_version', True).format(latest_version), QtWidgets.QMessageBox.Warning) close_forced_update_thread() forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 9c4ee82e..1fd52505 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -53,7 +53,7 @@ class UpdateChecker(QtCore.QObject): update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal() - update_invalid_version = QtCore.pyqtSignal() + update_invalid_version = QtCore.pyqtSignal(str) def __init__(self, common, onion, config=False): super(UpdateChecker, self).__init__() @@ -136,7 +136,7 @@ class UpdateChecker(QtCore.QObject): # This regex is: 1-3 dot-separated numeric components version_re = r"^(\d+\.)?(\d+\.)?(\d+)$" if not re.match(version_re, latest_version): - self.update_invalid_version.emit() + self.update_invalid_version.emit(latest_version) raise UpdateCheckerInvalidLatestVersion(latest_version) # Update the last checked timestamp (dropping the seconds and milliseconds) @@ -160,7 +160,7 @@ class UpdateThread(QtCore.QThread): update_available = QtCore.pyqtSignal(str, str, str) update_not_available = QtCore.pyqtSignal() update_error = QtCore.pyqtSignal() - update_invalid_version = QtCore.pyqtSignal() + update_invalid_version = QtCore.pyqtSignal(str) def __init__(self, common, onion, config=False, force=False): super(UpdateThread, self).__init__() @@ -203,7 +203,7 @@ class UpdateThread(QtCore.QThread): self.active = False self.update_error.emit() - def _update_invalid_version(self): + def _update_invalid_version(self, latest_version): self.common.log('UpdateThread', '_update_invalid_version') self.active = False - self.update_invalid_version.emit() + self.update_invalid_version.emit(latest_version) -- cgit v1.2.3-54-g00ecf From 808c5a33331c393a9ce92c2d34ff008576866cd0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Sep 2018 09:20:50 +1000 Subject: Truncate the length of the uploaded file name if it is longer than the width of the Upload window --- onionshare_gui/receive_mode/uploads.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 203a9804..09834156 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -38,6 +38,7 @@ class File(QtWidgets.QWidget): # 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() @@ -214,6 +215,8 @@ class Uploads(QtWidgets.QScrollArea): self.common = common self.common.log('Uploads', '__init__') + self.resizeEvent = None + self.uploads = {} self.setWindowTitle(strings._('gui_uploads', True)) @@ -292,3 +295,16 @@ class Uploads(QtWidgets.QScrollArea): self.no_uploads_label.show() 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(): + if item.filename_label_width > width: + item.filename_label.setText(item.filename[:25] + '[...]') + item.adjustSize() + if width > item.filename_label_width: + item.filename_label.setText(item.filename) + except: + pass -- cgit v1.2.3-54-g00ecf From 45a5a2ae634c623ba0c0ccc5dab2d1d6c8a2bbe0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 4 Sep 2018 14:26:47 +1000 Subject: Use the distribution's PySocks module instead of our custom version --- BUILD.md | 4 +- onionshare/onion.py | 1 - onionshare/socks.py | 530 --------------------------------------- onionshare_gui/update_checker.py | 2 +- 4 files changed, 3 insertions(+), 534 deletions(-) delete mode 100644 onionshare/socks.py diff --git a/BUILD.md b/BUILD.md index 77d5ee0e..f079806b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -11,9 +11,9 @@ cd onionshare Install the needed dependencies: -For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy` +For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-socks python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy` -For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4` +For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-pysocks python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/onionshare/onion.py b/onionshare/onion.py index 461af6a2..7cdd68ab 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -23,7 +23,6 @@ from stem import ProtocolError, SocketClosed from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure import os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex -from . import socks from . import common, strings from .settings import Settings diff --git a/onionshare/socks.py b/onionshare/socks.py deleted file mode 100644 index 809a4444..00000000 --- a/onionshare/socks.py +++ /dev/null @@ -1,530 +0,0 @@ -""" -SocksiPy - Python SOCKS module. -Version 1.5.0 - -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - -=============================================================================== - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) -for use in PyLoris (http://pyloris.sourceforge.net/) - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge - -Modifications made by Anorov (https://github.com/Anorov) --Forked and renamed to PySocks --Fixed issue with HTTP proxy failure checking (same bug that was in the old ___recvall() method) --Included SocksiPyHandler (sockshandler.py), to be used as a urllib2 handler, - courtesy of e000 (https://github.com/e000): https://gist.github.com/869791#file_socksipyhandler.py --Re-styled code to make it readable - -Aliased PROXY_TYPE_SOCKS5 -> SOCKS5 etc. - -Improved exception handling and output - -Removed irritating use of sequence indexes, replaced with tuple unpacked variables - -Fixed up Python 3 bytestring handling - chr(0x03).encode() -> b"\x03" - -Other general fixes --Added clarification that the HTTP proxy connection method only supports CONNECT-style tunneling HTTP proxies --Various small bug fixes -""" - -__version__ = "1.5.0" - -import socket -import struct - -PROXY_TYPE_SOCKS4 = SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = SOCKS5 = 2 -PROXY_TYPE_HTTP = HTTP = 3 - -PRINTABLE_PROXY_TYPES = {SOCKS4: "SOCKS4", SOCKS5: "SOCKS5", HTTP: "HTTP"} - -_orgsocket = _orig_socket = socket.socket - - -class ProxyError(IOError): - """ - socket_err contains original socket.error exception. - """ - def __init__(self, msg, socket_err=None): - self.msg = msg - self.socket_err = socket_err - - if socket_err: - self.msg = msg + ": {}".format(socket_err) - - def __str__(self): - return self.msg - - -class GeneralProxyError(ProxyError): - pass - - -class ProxyConnectionError(ProxyError): - pass - - -class SOCKS5AuthError(ProxyError): - pass - - -class SOCKS5Error(ProxyError): - pass - - -class SOCKS4Error(ProxyError): - pass - - -class HTTPError(ProxyError): - pass - - -SOCKS4_ERRORS = { - 0x5B: "Request rejected or failed", - 0x5C: "Request rejected because SOCKS server cannot connect to identd on the client", - 0x5D: "Request rejected because the client program and identd report different user-ids", -} - -SOCKS5_ERRORS = { - 0x01: "General SOCKS server failure", - 0x02: "Connection not allowed by ruleset", - 0x03: "Network unreachable", - 0x04: "Host unreachable", - 0x05: "Connection refused", - 0x06: "TTL expired", - 0x07: "Command not supported, or protocol error", - 0x08: "Address type not supported", -} - -DEFAULT_PORTS = { - SOCKS4: 1080, - SOCKS5: 1080, - HTTP: 8080, -} - - -def set_default_proxy(proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None): - """ - set_default_proxy(proxy_type, addr[, port[, rdns[, username, password]]]) - - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - socksocket.default_proxy = (proxy_type, addr.encode(), port, rdns, - username.encode() if username else None, - password.encode() if password else None) - -setdefaultproxy = set_default_proxy - - -def get_default_proxy(): - """ - Returns the default proxy, set by set_default_proxy. - """ - return socksocket.default_proxy - -getdefaultproxy = get_default_proxy - - -def wrap_module(module): - """ - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using set_default_proxy(...) first. - This will only work on modules that import socket directly into the namespace; - most of the Python Standard Library falls into this category. - """ - if socksocket.default_proxy: - module.socket.socket = socksocket - else: - raise GeneralProxyError("No default proxy specified") - -wrapmodule = wrap_module - - -def create_connection(dest_pair, proxy_type=None, proxy_addr=None, - proxy_port=None, proxy_username=None, - proxy_password=None, timeout=None): - """create_connection(dest_pair, **proxy_args) -> socket object - - Like socket.create_connection(), but connects to proxy - before returning the socket object. - - dest_pair - 2-tuple of (IP/hostname, port). - **proxy_args - Same args passed to socksocket.set_proxy(). - timeout - Optional socket timeout value, in seconds. - """ - sock = socksocket() - if isinstance(timeout, (int, float)): - sock.settimeout(timeout) - sock.set_proxy(proxy_type, proxy_addr, proxy_port, - proxy_username, proxy_password) - sock.connect(dest_pair) - return sock - - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - default_proxy = None - - def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): - _orig_socket.__init__(self, family, type, proto, _sock) - - if self.default_proxy: - self.proxy = self.default_proxy - else: - self.proxy = (None, None, None, None, None, None) - self.proxy_sockname = None - self.proxy_peername = None - - self.proxy_negotiators = { - SOCKS4: self._negotiate_SOCKS4, - SOCKS5: self._negotiate_SOCKS5, - HTTP: self._negotiate_HTTP, - } - - def _recvall(self, count): - """ - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - data = b"" - while len(data) < count: - d = self.recv(count - len(data)) - if not d: - raise GeneralProxyError("Connection closed unexpectedly") - data += d - return data - - def set_proxy(self, proxy_type=None, addr=None, port=None, rdns=True, username=None, password=None): - """set_proxy(proxy_type, addr[, port[, rdns[, username[, password]]]]) - Sets the proxy to be used. - - proxy_type - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - addr - The address of the server (IP or DNS). - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - rdns - Should DNS queries be performed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - username - Username to authenticate with to the server. - The default is no authentication. - password - Password to authenticate with to the server. - Only relevant when username is also provided. - """ - self.proxy = (proxy_type, addr.encode(), port, rdns, - username.encode() if username else None, - password.encode() if password else None) - - setproxy = set_proxy - - def get_proxy_sockname(self): - """ - Returns the bound IP address and port number at the proxy. - """ - return self.proxy_sockname - - getproxysockname = get_proxy_sockname - - def get_proxy_peername(self): - """ - Returns the IP and port number of the proxy. - """ - return _orig_socket.getpeername(self) - - getproxypeername = get_proxy_peername - - def get_peername(self): - """ - Returns the IP address and port number of the destination - machine (note: get_proxy_peername returns the proxy) - """ - return self.proxy_peername - - getpeername = get_peername - - def _negotiate_SOCKS5(self, dest_addr, dest_port): - """ - Negotiates a connection through a SOCKS5 server. - """ - proxy_type, addr, port, rdns, username, password = self.proxy - - # First we'll send the authentication packages we support. - if username and password: - # The username/password details were supplied to the - # set_proxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(b"\x05\x02\x00\x02") - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(b"\x05\x01\x00") - - # We'll receive the server's response to determine which - # method was selected - chosen_auth = self._recvall(2) - - if chosen_auth[0:1] != b"\x05": - # Note: string[i:i+1] is used because indexing of a bytestring - # via bytestring[i] yields an integer in Python 3 - raise GeneralProxyError("SOCKS5 proxy server sent invalid data") - - # Check the chosen authentication method - - if chosen_auth[1:2] == b"\x02": - # Okay, we need to perform a basic username/password - # authentication. - self.sendall(b"\x01" + chr(len(username)).encode() - + username - + chr(len(password)).encode() - + password) - auth_status = self._recvall(2) - if auth_status[0:1] != b"\x01": - # Bad response - raise GeneralProxyError("SOCKS5 proxy server sent invalid data") - if auth_status[1:2] != b"\x00": - # Authentication failed - raise SOCKS5AuthError("SOCKS5 authentication failed") - - # Otherwise, authentication succeeded - - # No authentication is required if 0x00 - elif chosen_auth[1:2] != b"\x00": - # Reaching here is always bad - if chosen_auth[1:2] == b"\xFF": - raise SOCKS5AuthError("All offered SOCKS5 authentication methods were rejected") - else: - raise GeneralProxyError("SOCKS5 proxy server sent invalid data") - - # Now we can request the actual connection - req = b"\x05\x01\x00" - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - addr_bytes = socket.inet_aton(dest_addr) - req += b"\x01" + addr_bytes - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if rdns: - # Resolve remotely - addr_bytes = None - req += b"\x03" + chr(len(dest_addr)).encode() + dest_addr.encode() - else: - # Resolve locally - addr_bytes = socket.inet_aton(socket.gethostbyname(dest_addr)) - req += b"\x01" + addr_bytes - - req += struct.pack(">H", dest_port) - self.sendall(req) - - # Get the response - resp = self._recvall(4) - if resp[0:1] != b"\x05": - raise GeneralProxyError("SOCKS5 proxy server sent invalid data") - - status = ord(resp[1:2]) - if status != 0x00: - # Connection failed: server returned an error - error = SOCKS5_ERRORS.get(status, "Unknown error") - raise SOCKS5Error("{:#04x}: {}".format(status, error)) - - # Get the bound address/port - if resp[3:4] == b"\x01": - bound_addr = self._recvall(4) - elif resp[3:4] == b"\x03": - resp += self.recv(1) - bound_addr = self._recvall(ord(resp[4:5])) - else: - raise GeneralProxyError("SOCKS5 proxy server sent invalid data") - - bound_port = struct.unpack(">H", self._recvall(2))[0] - self.proxy_sockname = bound_addr, bound_port - if addr_bytes: - self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port - else: - self.proxy_peername = dest_addr, dest_port - - def _negotiate_SOCKS4(self, dest_addr, dest_port): - """ - Negotiates a connection through a SOCKS4 server. - """ - proxy_type, addr, port, rdns, username, password = self.proxy - - # Check if the destination address provided is an IP address - remote_resolve = False - try: - addr_bytes = socket.inet_aton(dest_addr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if rdns: - addr_bytes = b"\x00\x00\x00\x01" - remote_resolve = True - else: - addr_bytes = socket.inet_aton(socket.gethostbyname(dest_addr)) - - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, dest_port) + addr_bytes - - # The username parameter is considered userid for SOCKS4 - if username: - req += username - req += b"\x00" - - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if remote_resolve: - req += dest_addr.encode() + b"\x00" - self.sendall(req) - - # Get the response from the server - resp = self._recvall(8) - if resp[0:1] != b"\x00": - # Bad data - raise GeneralProxyError("SOCKS4 proxy server sent invalid data") - - status = ord(resp[1:2]) - if status != 0x5A: - # Connection failed: server returned an error - error = SOCKS4_ERRORS.get(status, "Unknown error") - raise SOCKS4Error("{:#04x}: {}".format(status, error)) - - # Get the bound address/port - self.proxy_sockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) - if remote_resolve: - self.proxy_peername = socket.inet_ntoa(addr_bytes), dest_port - else: - self.proxy_peername = dest_addr, dest_port - - def _negotiate_HTTP(self, dest_addr, dest_port): - """ - Negotiates a connection through an HTTP server. - NOTE: This currently only supports HTTP CONNECT-style proxies. - """ - proxy_type, addr, port, rdns, username, password = self.proxy - - # If we need to resolve locally, we do this now - addr = dest_addr if rdns else socket.gethostbyname(dest_addr) - - self.sendall(b"CONNECT " + addr.encode() + b":" + str(dest_port).encode() + - b" HTTP/1.1\r\n" + b"Host: " + dest_addr.encode() + b"\r\n\r\n") - - # We just need the first line to check if the connection was successful - fobj = self.makefile() - status_line = fobj.readline() - fobj.close() - - if not status_line: - raise GeneralProxyError("Connection closed unexpectedly") - - try: - proto, status_code, status_msg = status_line.split(" ", 2) - except ValueError: - raise GeneralProxyError("HTTP proxy server sent invalid response") - - if not proto.startswith("HTTP/"): - raise GeneralProxyError("Proxy server does not appear to be an HTTP proxy") - - try: - status_code = int(status_code) - except ValueError: - raise HTTPError("HTTP proxy server did not return a valid HTTP status") - - if status_code != 200: - error = "{}: {}".format(status_code, status_msg) - if status_code in (400, 403, 405): - # It's likely that the HTTP proxy server does not support the CONNECT tunneling method - error += ("\n[*] Note: The HTTP proxy server may not be supported by PySocks" - " (must be a CONNECT tunnel proxy)") - raise HTTPError(error) - - self.proxy_sockname = (b"0.0.0.0", 0) - self.proxy_peername = addr, dest_port - - def connect(self, dest_pair): - """ - Connects to the specified destination through a proxy. - Uses the same API as socket's connect(). - To select the proxy server, use set_proxy(). - - dest_pair - 2-tuple of (IP/hostname, port). - """ - proxy_type, proxy_addr, proxy_port, rdns, username, password = self.proxy - dest_addr, dest_port = dest_pair - - # Do a minimal input check first - if (not isinstance(dest_pair, (list, tuple)) - or len(dest_pair) != 2 - or not isinstance(dest_addr, type("")) - or not isinstance(dest_port, int)): - raise GeneralProxyError("Invalid destination-connection (host, port) pair") - - if proxy_type is None: - # Treat like regular socket object - _orig_socket.connect(self, (dest_addr, dest_port)) - return - - proxy_port = proxy_port or DEFAULT_PORTS.get(proxy_type) - if not proxy_port: - raise GeneralProxyError("Invalid proxy type") - - try: - # Initial connection to proxy server - _orig_socket.connect(self, (proxy_addr, proxy_port)) - - except socket.error as error: - # Error while connecting to proxy - self.close() - proxy_server = "{}:{}".format(proxy_addr.decode(), proxy_port) - printable_type = PRINTABLE_PROXY_TYPES[proxy_type] - - msg = "Error connecting to {} proxy {}".format(printable_type, - proxy_server) - raise ProxyConnectionError(msg, error) - - else: - # Connected to proxy server, now negotiate - try: - # Calls negotiate_{SOCKS4, SOCKS5, HTTP} - self.proxy_negotiators[proxy_type](dest_addr, dest_port) - except socket.error as error: - # Wrap socket errors - self.close() - raise GeneralProxyError("Socket error", error) - except ProxyError: - # Protocol error while negotiating with proxy - self.close() - raise diff --git a/onionshare_gui/update_checker.py b/onionshare_gui/update_checker.py index 9c4ee82e..c573b1b9 100644 --- a/onionshare_gui/update_checker.py +++ b/onionshare_gui/update_checker.py @@ -19,9 +19,9 @@ along with this program. If not, see . """ from PyQt5 import QtCore import datetime, time, socket, re, platform +import socks from distutils.version import LooseVersion as Version -from onionshare import socks from onionshare.settings import Settings from onionshare.onion import Onion -- cgit v1.2.3-54-g00ecf From 85fa44a01f05349fa75261b9dcece206c3296755 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 4 Sep 2018 14:49:56 +1000 Subject: Pass self to help_clicked function in systray, so we can use self.common --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index a6c20e33..6dfbaf4e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -277,7 +277,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True)) self.settingsAction.triggered.connect(self.open_settings) self.helpAction = menu.addAction(strings._('gui_settings_button_help', True)) - self.helpAction.triggered.connect(SettingsDialog.help_clicked) + self.helpAction.triggered.connect(lambda: SettingsDialog.help_clicked(self)) self.exitAction = menu.addAction(strings._('systray_menu_exit', True)) self.exitAction.triggered.connect(self.close) -- cgit v1.2.3-54-g00ecf From f9e614eba125846dc8739fb8e09f04a6f432da36 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 11:35:28 +1000 Subject: Update cryptography dependency to 2.3.1 --- install/requirements-windows.txt | 2 +- install/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 611edf3c..f016c523 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,5 +1,5 @@ click==6.7 -cryptography==2.1.4 +cryptography==2.3.1 Flask==0.12.2 future==0.16.0 itsdangerous==0.24 diff --git a/install/requirements.txt b/install/requirements.txt index 964030e8..15e9a108 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,5 +1,5 @@ click==6.7 -cryptography==2.1.4 +cryptography==2.3.1 Flask==0.12.2 itsdangerous==0.24 Jinja2==2.10 -- cgit v1.2.3-54-g00ecf From 8955ce0699f391f1950d2029e4aeaf615245285f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:21:38 +1000 Subject: Refactor the onionkey stuff to be more like @maqp's revised version (thanks) --- onionshare/onionkey.py | 54 ++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py index 89c781ab..e9dcc9c4 100644 --- a/onionshare/onionkey.py +++ b/onionshare/onionkey.py @@ -37,39 +37,40 @@ from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -b = 256 - -def bit(h, i): - return (h[i // 8] >> (i % 8)) & 1 - - -def encodeint(y): - bits = [(y >> i) & 1 for i in range(b)] - return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) +def stem_compatible_base64_blob_from_private_key(private_key_seed: bytes) -> str: + """ + Provides a base64-encoded private key for v3-style Onions. + """ + b = 256 + def bit(h: bytes, i: int) -> int: + return (h[i // 8] >> (i % 8)) & 1 -def H(m): - return hashlib.sha512(m).digest() + def encode_int(y: int) -> bytes: + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) + def expand_private_key(sk: bytes) -> bytes: + h = hashlib.sha512(sk).digest() + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) + assert len(k) == 32 + return encode_int(a) + k -def expandSK(sk): - h = H(sk) - a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) - k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) - assert len(k) == 32 - return encodeint(a) + k + expanded_private_key = expand_private_key(private_key_seed) + return base64.b64encode(expanded_private_key).decode() -def onion_url_from_private_key(private_key): +def onion_url_from_private_key(private_key_seed: bytes) -> str: """ Derives the public key (.onion hostname) from a v3-style Onion private key. """ - private_key = nacl.signing.SigningKey(seed=private_key) - pubkey = bytes(private_key.verify_key) + signing_key = nacl.signing.SigningKey(seed=private_key_seed) + public_key = bytes(signing_key.verify_key) version = b'\x03' - checksum = hashlib.sha3_256(b".onion checksum" + pubkey + version).digest()[:2] - onion_address = "http://{}.onion".format(base64.b32encode(pubkey + checksum + version).decode().lower()) + checksum = hashlib.sha3_256(b".onion checksum" + public_key + version).digest()[:2] + onion_address = "http://{}.onion".format(base64.b32encode(public_key + checksum + version).decode().lower()) return onion_address @@ -78,10 +79,10 @@ def generate_v3_private_key(): Generates a private and public key for use with v3 style Onions. Returns both the private key as well as the public key (.onion hostname) """ - secretKey = os.urandom(32) - expandedSecretKey = expandSK(secretKey) - private_key = base64.b64encode(expandedSecretKey).decode() - return (private_key, onion_url_from_private_key(secretKey)) + private_key_seed = os.urandom(32) + private_key = stem_compatible_base64_blob_from_private_key(private_key_seed) + return (private_key, onion_url_from_private_key(private_key_seed)) + def generate_v2_private_key(): """ @@ -109,6 +110,7 @@ def generate_v2_private_key(): return (serialized_key, onion_url) + def is_v2_key(key): """ Helper function for determining if a key is RSA1024 (v2) or not. -- cgit v1.2.3-54-g00ecf From 0b0eef724561eec094482c1c7961e40bc62fbb13 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:29:48 +1000 Subject: More clarity for the returned values in generate_v3_private_key(), also more consistent with generate_v2_private_key() --- onionshare/onionkey.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py index e9dcc9c4..d2c6ad17 100644 --- a/onionshare/onionkey.py +++ b/onionshare/onionkey.py @@ -81,7 +81,8 @@ def generate_v3_private_key(): """ private_key_seed = os.urandom(32) private_key = stem_compatible_base64_blob_from_private_key(private_key_seed) - return (private_key, onion_url_from_private_key(private_key_seed)) + onion_url = onion_url_from_private_key(private_key_seed) + return (private_key, onion_url) def generate_v2_private_key(): -- cgit v1.2.3-54-g00ecf From 25eed81b004532481a6e794dca6698a6adc08920 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 16:35:24 +1000 Subject: Fixing a future check for persistent v3 onions (still disabled for now) --- onionshare/onion.py | 17 ++++++++--------- share/locale/en.json | 1 + 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 18ebeb5d..031f418a 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -446,11 +446,10 @@ class Onion(object): basic_auth = None if self.settings.get('private_key'): - try: - # is the key a v2 key? - key = onionkey.is_v2_key(self.settings.get('private_key')) + key_content = self.settings.get('private_key') + # is the key a v2 key? + if onionkey.is_v2_key(key_content): key_type = "RSA1024" - key_content = self.settings.get('private_key') # The below section is commented out because re-publishing # a pre-prepared v3 private key is currently unstable in Tor. # This is fixed upstream but won't reach stable until 0.3.5 @@ -460,11 +459,11 @@ class Onion(object): # v3 onions, which should not be possible via the GUI settings # anyway. # Our ticket: https://github.com/micahflee/onionshare/issues/677 - except: - pass - # Assume it was a v3 key - # key_type = "ED25519-V3" - # key_content = self.settings.get('private_key') + # + # Assume it was a v3 key + # key_type = "ED25519-V3" + else: + raise TorErrorProtocolError(strings._('error_invalid_private_key')) self.common.log('Onion', 'Starting a hidden service with a saved private key') else: # Work out if we can support v3 onion services, which are preferred diff --git a/share/locale/en.json b/share/locale/en.json index d73fb369..06301e80 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -139,6 +139,7 @@ "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "error_invalid_private_key": "This private key type is unsupported", "connecting_to_tor": "Connecting to the Tor network", "update_available": "A new version of OnionShare is available. Click here to download it.

    Installed version: {}
    Latest version: {}", "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.", -- cgit v1.2.3-54-g00ecf From e54a1473ce277ee6796b6a2ce23e3d09977372eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 15 Sep 2018 11:36:34 +1000 Subject: Don't check slug candidate in public mode --- onionshare/web.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 8044dbaf..bc06ca8c 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -143,7 +143,8 @@ class Web(object): """ @self.app.route("/") def index(slug_candidate): - self.check_slug_candidate(slug_candidate) + if not self.common.settings.get('public_mode'): + self.check_slug_candidate(slug_candidate) return index_logic() @self.app.route("/") @@ -186,7 +187,8 @@ class Web(object): @self.app.route("//download") def download(slug_candidate): - self.check_slug_candidate(slug_candidate) + if not self.common.settings.get('public_mode'): + self.check_slug_candidate(slug_candidate) return download_logic() @self.app.route("/download") @@ -329,7 +331,8 @@ class Web(object): @self.app.route("/") def index(slug_candidate): - self.check_slug_candidate(slug_candidate) + if not self.common.settings.get('public_mode'): + self.check_slug_candidate(slug_candidate) return index_logic() @self.app.route("/") @@ -427,7 +430,8 @@ class Web(object): @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): - self.check_slug_candidate(slug_candidate) + if not self.common.settings.get('public_mode'): + self.check_slug_candidate(slug_candidate) return upload_logic(slug_candidate) @self.app.route("/upload", methods=['POST']) @@ -448,7 +452,8 @@ class Web(object): @self.app.route("//close", methods=['POST']) def close(slug_candidate): - self.check_slug_candidate(slug_candidate) + if not self.common.settings.get('public_mode'): + self.check_slug_candidate(slug_candidate) return close_logic(slug_candidate) @self.app.route("/close", methods=['POST']) -- cgit v1.2.3-54-g00ecf From 30ee2290d78837c1c8dfabdc31a9ddfb7098a386 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 15 Sep 2018 16:07:08 +1000 Subject: Fix bug where lack of stealth mode re-enabled v2 legacy checkbox even if persistence was still enabled --- onionshare_gui/settings_dialog.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b2515a69..24fea800 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -463,7 +463,8 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setEnabled(True) + if not save_private_key: + self.use_legacy_v2_onions_checkbox.setEnabled(True) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: -- cgit v1.2.3-54-g00ecf From 52981b6f98d2a29ec1e382da670c03fb876bdcec Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:05:40 -0700 Subject: Fix bad merge in license comment --- dev_scripts/onionshare | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev_scripts/onionshare b/dev_scripts/onionshare index 8b6989ef..29cdedcc 100755 --- a/dev_scripts/onionshare +++ b/dev_scripts/onionshare @@ -3,7 +3,7 @@ """ OnionShare | https://onionshare.org/ -Copyright (C) 2014-2018 Micah Lee >>>>>>> develop +Copyright (C) 2014-2018 Micah Lee 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 -- cgit v1.2.3-54-g00ecf From 9815c612ebff3f099997301c4964e5f163001d40 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:47:42 -0700 Subject: Check for public_mode in the check_slug_candidate function, to make 404 errors work again during public mode --- onionshare/web.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index bc06ca8c..221c2c53 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -143,8 +143,7 @@ class Web(object): """ @self.app.route("/") def index(slug_candidate): - if not self.common.settings.get('public_mode'): - self.check_slug_candidate(slug_candidate) + self.check_slug_candidate(slug_candidate) return index_logic() @self.app.route("/") @@ -187,8 +186,7 @@ class Web(object): @self.app.route("//download") def download(slug_candidate): - if not self.common.settings.get('public_mode'): - self.check_slug_candidate(slug_candidate) + self.check_slug_candidate(slug_candidate) return download_logic() @self.app.route("/download") @@ -331,8 +329,7 @@ class Web(object): @self.app.route("/") def index(slug_candidate): - if not self.common.settings.get('public_mode'): - self.check_slug_candidate(slug_candidate) + self.check_slug_candidate(slug_candidate) return index_logic() @self.app.route("/") @@ -430,8 +427,7 @@ class Web(object): @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): - if not self.common.settings.get('public_mode'): - self.check_slug_candidate(slug_candidate) + self.check_slug_candidate(slug_candidate) return upload_logic(slug_candidate) @self.app.route("/upload", methods=['POST']) @@ -452,8 +448,7 @@ class Web(object): @self.app.route("//close", methods=['POST']) def close(slug_candidate): - if not self.common.settings.get('public_mode'): - self.check_slug_candidate(slug_candidate) + self.check_slug_candidate(slug_candidate) return close_logic(slug_candidate) @self.app.route("/close", methods=['POST']) @@ -574,10 +569,14 @@ class Web(object): self.app.logger.addHandler(log_handler) def check_slug_candidate(self, slug_candidate, slug_compare=None): - if not slug_compare: - slug_compare = self.slug - if not hmac.compare_digest(slug_compare, slug_candidate): + self.common.log('Web', 'check_slug_candidate: slug_candidate={}, slug_compare={}'.format(slug_candidate, slug_compare)) + if self.common.settings.get('public_mode'): abort(404) + else: + if not slug_compare: + slug_compare = self.slug + if not hmac.compare_digest(slug_compare, slug_candidate): + abort(404) def force_shutdown(self): """ -- cgit v1.2.3-54-g00ecf From 73f09f14c4a1e05df5d7f3c5058129cfa0dd099f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:52:53 -0700 Subject: Make 404 error page look better, and remove the text that it's probably a typo, because in public mode that isn't necessarily true --- share/static/css/style.css | 8 ++++---- share/templates/404.html | 20 +++++++++++++------- share/templates/closed.html | 8 ++++---- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/share/static/css/style.css b/share/static/css/style.css index 7f5f4310..fd10ecdf 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -172,23 +172,23 @@ li.info { min-height: 400px; } -.closed { +.info { text-align: center; } -.closed img { +.info img { width: 120px; height: 120px; } -.closed .closed-header { +.info .info-header { font-size: 30px; font-weight: normal; color: #666666; margin: 0 0 10px 0; } -.closed .closed-description { +.info .info-description { color: #666666; margin: 0 0 20px 0; } diff --git a/share/templates/404.html b/share/templates/404.html index b704f9f2..264ca517 100644 --- a/share/templates/404.html +++ b/share/templates/404.html @@ -1,10 +1,16 @@ - - OnionShare: Error 404 - - - -

    Error 404: You probably typed the OnionShare address wrong

    - + + OnionShare: 404 Not Found + + + + +
    +
    +

    +

    404 Not Found

    +
    +
    + diff --git a/share/templates/closed.html b/share/templates/closed.html index c34e0ee4..64c8b369 100644 --- a/share/templates/closed.html +++ b/share/templates/closed.html @@ -11,11 +11,11 @@

    OnionShare

    -
    -
    +
    +

    -

    Thank you for using OnionShare

    -

    You may now close this window.

    +

    Thank you for using OnionShare

    +

    You may now close this window.

    -- cgit v1.2.3-54-g00ecf From 6269f16034ace050013d0a0b33fc4cdd938facd5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 20:33:12 -0700 Subject: Update all pip dependency versions --- install/requirements-windows.txt | 12 ++++++++++++ install/requirements.txt | 19 ++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 6e03f6e8..5d88fec7 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,14 +1,26 @@ +altgraph==0.15 +certifi==2018.4.16 +chardet==3.0.4 click==6.7 Flask==0.12.2 future==0.16.0 +idna==2.7 itsdangerous==0.24 Jinja2==2.10 +macholib==1.9 MarkupSafe==1.0 +packaging==17.1 pefile==2017.11.5 PyInstaller==3.3.1 +pyparsing==2.2.0 +pypiwin32==223 PyQt5==5.9.2 PySocks==1.6.7 +python-dateutil==2.7.3 +pywin32==223 requests==2.19.1 sip==4.19.6 +six==1.11.0 stem==1.6.0 +urllib3==1.23 Werkzeug==0.14.1 diff --git a/install/requirements.txt b/install/requirements.txt index ed83b995..16179eb7 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,12 +1,21 @@ +altgraph==0.16.1 +certifi==2018.8.24 +chardet==3.0.4 click==6.7 -Flask==0.12.2 +Flask==1.0.2 +future==0.16.0 +idna==2.7 itsdangerous==0.24 Jinja2==2.10 +macholib==1.11 MarkupSafe==1.0 -PyInstaller==3.3.1 -PyQt5==5.9.2 -PySocks==1.6.7 +pefile==2018.8.8 +PyInstaller==3.4 +PyQt5==5.11.2 +PyQt5-sip==4.19.12 +PySocks==1.6.8 requests==2.19.1 -sip==4.19.6 +sip==4.19.8 stem==1.6.0 +urllib3==1.23 Werkzeug==0.14.1 -- cgit v1.2.3-54-g00ecf From d8566c2d7868e003faef1228b76fc35940891304 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 13:54:47 +1000 Subject: Move stealth to general options, and add hyperlinks for more info for this and legacy addresses --- onionshare_gui/settings_dialog.py | 62 +++++++++++++++++++-------------------- share/locale/en.json | 10 +++---- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 5b052375..856ca993 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -58,6 +58,10 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() 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)) + use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) + use_legacy_v2_onions_label.setWordWrap(True) + use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + use_legacy_v2_onions_label.setOpenExternalLinks(True) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -70,11 +74,37 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + # Stealth + self.stealth_checkbox = QtWidgets.QCheckBox() + self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) + self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) + stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + stealth_details.setWordWrap(True) + stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + stealth_details.setOpenExternalLinks(True) + stealth_details.setMinimumSize(stealth_details.sizeHint()) + + hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) + hidservauth_details.setWordWrap(True) + hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) + hidservauth_details.hide() + + self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) + self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) + self.hidservauth_copy_button.hide() + # General options layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) + general_group_layout.addWidget(use_legacy_v2_onions_label) general_group_layout.addWidget(self.save_private_key_checkbox) general_group_layout.addWidget(self.public_mode_checkbox) + general_group_layout.addWidget(self.stealth_checkbox) + general_group_layout.addWidget(stealth_details) + general_group_layout.addWidget(hidservauth_details) + general_group_layout.addWidget(self.hidservauth_copy_button) + general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label", True)) general_group.setLayout(general_group_layout) @@ -120,37 +150,6 @@ class SettingsDialog(QtWidgets.QDialog): receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group.setLayout(receiving_group_layout) - # Stealth options - - # Stealth - stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - stealth_details.setWordWrap(True) - stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - stealth_details.setOpenExternalLinks(True) - stealth_details.setMinimumSize(stealth_details.sizeHint()) - self.stealth_checkbox = QtWidgets.QCheckBox() - self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) - self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - - hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) - hidservauth_details.setWordWrap(True) - hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) - hidservauth_details.hide() - - self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) - self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) - self.hidservauth_copy_button.hide() - - # Stealth options layout - stealth_group_layout = QtWidgets.QVBoxLayout() - stealth_group_layout.addWidget(stealth_details) - stealth_group_layout.addWidget(self.stealth_checkbox) - stealth_group_layout.addWidget(hidservauth_details) - stealth_group_layout.addWidget(self.hidservauth_copy_button) - stealth_group = QtWidgets.QGroupBox(strings._("gui_settings_stealth_label", True)) - stealth_group.setLayout(stealth_group_layout) - # Automatic updates options # Autoupdate @@ -383,7 +382,6 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(general_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) - left_col_layout.addWidget(stealth_group) left_col_layout.addWidget(autoupdate_group) left_col_layout.addStretch() diff --git a/share/locale/en.json b/share/locale/en.json index e8649106..0239037a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -71,9 +71,8 @@ "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", - "gui_settings_stealth_label": "Stealth (advanced)", - "gui_settings_stealth_option": "Create stealth onion services", - "gui_settings_stealth_option_details": "This makes OnionShare more secure, but also more difficult for the recipient to connect to.

    NOTE: currently this only works with v2 onions.
    More information.", + "gui_settings_stealth_option": "Create stealth onion services (legacy)", + "gui_settings_stealth_option_details": "(what's this?)", "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.", "gui_settings_autoupdate_label": "Check for upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -139,8 +138,9 @@ "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", - "gui_use_legacy_v2_onions_checkbox": "Use legacy (v2) .onion addresses?", - "gui_save_private_key_checkbox": "Use a persistent (v2 only) address\n(unchecking will delete any saved addresses)", + "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", + "gui_use_legacy_v2_onions_label": "(what's this?)", + "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", -- cgit v1.2.3-54-g00ecf From 95f097eae32ba2a46f9d483a6e8b5584f56b4ae5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:00:41 +1000 Subject: Move the hyperlink labels into HBox layouts with the checkboxes --- onionshare_gui/settings_dialog.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 856ca993..2897b944 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -62,6 +62,12 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_label.setWordWrap(True) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) + use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() + use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) + use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) + use_legacy_v2_onions_layout.addStretch() + use_legacy_v2_onions_widget = QtWidgets.QWidget() + use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -79,11 +85,18 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - stealth_details = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - stealth_details.setWordWrap(True) - stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - stealth_details.setOpenExternalLinks(True) - stealth_details.setMinimumSize(stealth_details.sizeHint()) + use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + use_stealth_label.setWordWrap(True) + use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + use_stealth_label.setOpenExternalLinks(True) + use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) + + use_stealth_layout = QtWidgets.QHBoxLayout() + use_stealth_layout.addWidget(self.stealth_checkbox) + use_stealth_layout.addWidget(use_stealth_label) + use_stealth_layout.addStretch() + use_stealth_widget = QtWidgets.QWidget() + use_stealth_widget.setLayout(use_stealth_layout) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -96,12 +109,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(self.use_legacy_v2_onions_checkbox) - general_group_layout.addWidget(use_legacy_v2_onions_label) + general_group_layout.addWidget(use_legacy_v2_onions_widget) general_group_layout.addWidget(self.save_private_key_checkbox) general_group_layout.addWidget(self.public_mode_checkbox) - general_group_layout.addWidget(self.stealth_checkbox) - general_group_layout.addWidget(stealth_details) + general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) -- cgit v1.2.3-54-g00ecf From 026322b4585cddd8c03e903484d07abf4248411b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:06:55 +1000 Subject: Fix margins on HBoxLayouts in settings --- onionshare_gui/settings_dialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 2897b944..f8201fea 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -66,6 +66,7 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_layout.addWidget(self.use_legacy_v2_onions_checkbox) use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() + use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) use_legacy_v2_onions_widget = QtWidgets.QWidget() use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) @@ -95,6 +96,7 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() + use_stealth_layout.setContentsMargins(0,0,0,0) use_stealth_widget = QtWidgets.QWidget() use_stealth_widget.setLayout(use_stealth_layout) -- cgit v1.2.3-54-g00ecf From 6efa5f15b731a8eea25932504bc8c8e564f2a3bc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:18:44 +1000 Subject: Fix public mode tests for 404 --- test/test_onionshare_web.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 0819010a..2209a0fd 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -173,14 +173,15 @@ class TestWeb: common_obj.settings.set('public_mode', True) with web.app.test_client() as c: - # Upload page should be accessible from both / and /[slug] + # Upload page should be accessible from / res = c.get('/') data1 = res.get_data() assert res.status_code == 200 + # /[slug] should be a 404 res = c.get('/{}'.format(web.slug)) data2 = res.get_data() - assert res.status_code == 200 + assert res.status_code == 404 def test_public_mode_off(self, common_obj): web = web_obj(common_obj, True) @@ -192,7 +193,7 @@ class TestWeb: data1 = res.get_data() assert res.status_code == 404 - # Upload page should be accessible from both /[slug] + # Upload page should be accessible from /[slug] res = c.get('/{}'.format(web.slug)) data2 = res.get_data() assert res.status_code == 200 -- cgit v1.2.3-54-g00ecf From f90e96e21a4cfb6ca52c8e102b4ac57bf09e19b7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 22:04:08 -0700 Subject: Monkeypatch flask to suppress output that isn't applicable to OnionShare --- onionshare/web.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/onionshare/web.py b/onionshare/web.py index f67a4b16..68b8fbc2 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -32,6 +32,7 @@ import io from distutils.version import LooseVersion as Version from urllib.request import urlopen +import flask from flask import ( Flask, Response, Request, request, render_template, abort, make_response, flash, redirect, __version__ as flask_version @@ -40,6 +41,15 @@ from werkzeug.utils import secure_filename from . import strings, common + +# Stub out flask's show_server_banner function, to avoiding showing warnings that +# are not applicable to OnionShare +def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): + pass + +flask.cli.show_server_banner = stubbed_show_server_banner + + class Web(object): """ The Web object is the OnionShare web server, powered by flask -- cgit v1.2.3-54-g00ecf From 7bd897d19ec0dab18519f6b3c48ebdf40138eaba Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 15:15:40 +1000 Subject: Don't show the Flash shutdown slug route in the status bar as if it were an unexpected 404 route --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index cb9adbf5..65875bc0 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -401,7 +401,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) if event["type"] == Web.REQUEST_OTHER: - if event["path"] != '/favicon.ico': + 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"])) mode.timer_callback() -- cgit v1.2.3-54-g00ecf From 05a9e3b158aa39d7d978fb1ba13b72d4322616b1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 15:41:50 +1000 Subject: Bump windows versions of pip packages to match those of OS X --- install/requirements-windows.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 5d88fec7..005acc9f 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -1,25 +1,25 @@ -altgraph==0.15 -certifi==2018.4.16 +altgraph==0.16.1 +certifi==2018.8.24 chardet==3.0.4 click==6.7 -Flask==0.12.2 +Flask==1.0.2 future==0.16.0 idna==2.7 itsdangerous==0.24 Jinja2==2.10 -macholib==1.9 +macholib==1.11 MarkupSafe==1.0 packaging==17.1 -pefile==2017.11.5 -PyInstaller==3.3.1 +pefile==2018.8.8 +PyInstaller==3.4 pyparsing==2.2.0 pypiwin32==223 -PyQt5==5.9.2 -PySocks==1.6.7 +PyQt5==5.11.2 +PySocks==1.6.8 python-dateutil==2.7.3 pywin32==223 requests==2.19.1 -sip==4.19.6 +sip==4.19.8 six==1.11.0 stem==1.6.0 urllib3==1.23 -- cgit v1.2.3-54-g00ecf From 4777c45ad8f7aa987e217f2b18b89f6937de3eae Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 16 Sep 2018 13:50:30 -0700 Subject: Fix suppressing the shutdown_slug message --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 65875bc0..b63119bb 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -401,7 +401,7 @@ class OnionShareGui(QtWidgets.QMainWindow): Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) 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"])) mode.timer_callback() -- cgit v1.2.3-54-g00ecf From 8c3c0eb02bb608e35519d18faf83ffa2f24ea336 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:43:19 +1000 Subject: Use 'settings' rather than 'options' in the SettingsDialog labels --- share/locale/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index 0239037a..df4d03e6 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -79,8 +79,8 @@ "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", "gui_settings_autoupdate_check_button": "Check For Upgrades", - "gui_settings_general_label": "General options", - "gui_settings_sharing_label": "Sharing options", + "gui_settings_general_label": "General settings", + "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare", @@ -91,7 +91,7 @@ "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_socks_label": "SOCKS port", - "gui_settings_authenticate_label": "Tor authentication options", + "gui_settings_authenticate_label": "Tor authentication settings", "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", @@ -166,7 +166,7 @@ "receive_mode_received_file": "Received file: {}", "gui_mode_share_button": "Share Files", "gui_mode_receive_button": "Receive Files", - "gui_settings_receiving_label": "Receiving options", + "gui_settings_receiving_label": "Receiving settings", "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", -- cgit v1.2.3-54-g00ecf From 7c55f0adaea6257fc3758f74390ec297114a4b5e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:44:56 +1000 Subject: Reorder the general settings --- onionshare_gui/settings_dialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index f8201fea..cee119f4 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -81,6 +81,11 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + # Whether or not to use a shutdown timer + self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() + self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) @@ -111,9 +116,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() + general_group_layout.addWidget(self.public_mode_checkbox) + general_group_layout.addWidget(self.shutdown_timeout_checkbox) general_group_layout.addWidget(use_legacy_v2_onions_widget) general_group_layout.addWidget(self.save_private_key_checkbox) - general_group_layout.addWidget(self.public_mode_checkbox) general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) @@ -128,15 +134,9 @@ class SettingsDialog(QtWidgets.QDialog): 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)) - # Whether or not to use a shutdown timer - self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() - self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - # Sharing options layout sharing_group_layout = QtWidgets.QVBoxLayout() sharing_group_layout.addWidget(self.close_after_first_download_checkbox) - sharing_group_layout.addWidget(self.shutdown_timeout_checkbox) sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label", True)) sharing_group.setLayout(sharing_group_layout) -- cgit v1.2.3-54-g00ecf From 6c01d7a2daa360ad768932109bb023f2a76c9ced Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:01:30 +1000 Subject: Add 'what's this' labels to each General Setting --- onionshare_gui/settings_dialog.py | 60 +++++++++++++++++++++++++++++---------- share/locale/en.json | 5 +++- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index cee119f4..b33aa94c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -54,12 +54,43 @@ class SettingsDialog(QtWidgets.QDialog): # General options + # Use a slug or not ('public mode') + self.public_mode_checkbox = QtWidgets.QCheckBox() + self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) + self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) + public_mode_label = QtWidgets.QLabel(strings._("gui_settings_public_mode_details", True)) + public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + public_mode_label.setOpenExternalLinks(True) + public_mode_label.setMinimumSize(public_mode_label.sizeHint()) + public_mode_layout = QtWidgets.QHBoxLayout() + public_mode_layout.addWidget(self.public_mode_checkbox) + public_mode_layout.addWidget(public_mode_label) + public_mode_layout.addStretch() + public_mode_layout.setContentsMargins(0,0,0,0) + public_mode_widget = QtWidgets.QWidget() + public_mode_widget.setLayout(public_mode_layout) + + # Whether or not to use a shutdown ('auto-stop') timer + self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() + self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_shutdown_timeout_details", True)) + shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + shutdown_timeout_label.setOpenExternalLinks(True) + shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint()) + shutdown_timeout_layout = QtWidgets.QHBoxLayout() + shutdown_timeout_layout.addWidget(self.shutdown_timeout_checkbox) + shutdown_timeout_layout.addWidget(shutdown_timeout_label) + shutdown_timeout_layout.addStretch() + shutdown_timeout_layout.setContentsMargins(0,0,0,0) + shutdown_timeout_widget = QtWidgets.QWidget() + shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() 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)) use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) - use_legacy_v2_onions_label.setWordWrap(True) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() @@ -75,16 +106,16 @@ class SettingsDialog(QtWidgets.QDialog): 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.clicked.connect(self.save_private_key_checkbox_clicked) - - # Use a slug - self.public_mode_checkbox = QtWidgets.QCheckBox() - self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - - # Whether or not to use a shutdown timer - self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() - self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) + save_private_key_label = QtWidgets.QLabel(strings._("gui_save_private_key_label", True)) + save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + save_private_key_label.setOpenExternalLinks(True) + save_private_key_layout = QtWidgets.QHBoxLayout() + save_private_key_layout.addWidget(self.save_private_key_checkbox) + save_private_key_layout.addWidget(save_private_key_label) + save_private_key_layout.addStretch() + save_private_key_layout.setContentsMargins(0,0,0,0) + save_private_key_widget = QtWidgets.QWidget() + save_private_key_widget.setLayout(save_private_key_layout) # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() @@ -92,7 +123,6 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) - use_stealth_label.setWordWrap(True) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) @@ -116,10 +146,10 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(self.public_mode_checkbox) - general_group_layout.addWidget(self.shutdown_timeout_checkbox) + general_group_layout.addWidget(public_mode_widget) + general_group_layout.addWidget(shutdown_timeout_widget) general_group_layout.addWidget(use_legacy_v2_onions_widget) - general_group_layout.addWidget(self.save_private_key_checkbox) + general_group_layout.addWidget(save_private_key_widget) general_group_layout.addWidget(use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) diff --git a/share/locale/en.json b/share/locale/en.json index df4d03e6..9c7501cf 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -109,6 +109,7 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", + "gui_settings_shutdown_timeout_details": "(what's this?)", "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", @@ -141,6 +142,7 @@ "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_use_legacy_v2_onions_label": "(what's this?)", "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", + "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", @@ -170,7 +172,8 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", - "gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)", + "gui_settings_public_mode_checkbox": "Public mode", + "gui_settings_public_mode_details": "(what's this?)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", -- cgit v1.2.3-54-g00ecf From ff8b7df5a55a37e3a486cbb1bf48bf75220c7c1b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:02:35 +1000 Subject: reduce verbosity of persistent mode label --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index 9c7501cf..07a01464 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -141,7 +141,7 @@ "share_via_onionshare": "Share via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_use_legacy_v2_onions_label": "(what's this?)", - "gui_save_private_key_checkbox": "Use a persistent address (legacy)\n(unchecking will delete any saved addresses)", + "gui_save_private_key_checkbox": "Use a persistent address (legacy)", "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", -- cgit v1.2.3-54-g00ecf From 6ed5c94df75101fc9f62a10fe46323369ffa2dbf Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:12:13 +1000 Subject: Hide the legacy settings if legacy mode is not enabled. Fix unrelated bug regarding displaying the HidServAuth copy button/label --- onionshare_gui/settings_dialog.py | 52 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b33aa94c..895cde13 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -67,8 +67,8 @@ class SettingsDialog(QtWidgets.QDialog): public_mode_layout.addWidget(public_mode_label) public_mode_layout.addStretch() public_mode_layout.setContentsMargins(0,0,0,0) - public_mode_widget = QtWidgets.QWidget() - public_mode_widget.setLayout(public_mode_layout) + self.public_mode_widget = QtWidgets.QWidget() + self.public_mode_widget.setLayout(public_mode_layout) # Whether or not to use a shutdown ('auto-stop') timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() @@ -83,13 +83,14 @@ class SettingsDialog(QtWidgets.QDialog): shutdown_timeout_layout.addWidget(shutdown_timeout_label) shutdown_timeout_layout.addStretch() shutdown_timeout_layout.setContentsMargins(0,0,0,0) - shutdown_timeout_widget = QtWidgets.QWidget() - shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + self.shutdown_timeout_widget = QtWidgets.QWidget() + self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() 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.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) @@ -98,8 +99,8 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions_layout.addWidget(use_legacy_v2_onions_label) use_legacy_v2_onions_layout.addStretch() use_legacy_v2_onions_layout.setContentsMargins(0,0,0,0) - use_legacy_v2_onions_widget = QtWidgets.QWidget() - use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) + self.use_legacy_v2_onions_widget = QtWidgets.QWidget() + self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) # Whether or not to save the Onion private key for reuse (persistent URL mode) self.save_private_key_checkbox = QtWidgets.QCheckBox() @@ -114,8 +115,8 @@ class SettingsDialog(QtWidgets.QDialog): save_private_key_layout.addWidget(save_private_key_label) save_private_key_layout.addStretch() save_private_key_layout.setContentsMargins(0,0,0,0) - save_private_key_widget = QtWidgets.QWidget() - save_private_key_widget.setLayout(save_private_key_layout) + self.save_private_key_widget = QtWidgets.QWidget() + self.save_private_key_widget.setLayout(save_private_key_layout) # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() @@ -126,14 +127,13 @@ class SettingsDialog(QtWidgets.QDialog): use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) - use_stealth_layout = QtWidgets.QHBoxLayout() use_stealth_layout.addWidget(self.stealth_checkbox) use_stealth_layout.addWidget(use_stealth_label) use_stealth_layout.addStretch() use_stealth_layout.setContentsMargins(0,0,0,0) - use_stealth_widget = QtWidgets.QWidget() - use_stealth_widget.setLayout(use_stealth_layout) + self.use_stealth_widget = QtWidgets.QWidget() + self.use_stealth_widget.setLayout(use_stealth_layout) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details.setWordWrap(True) @@ -146,11 +146,11 @@ class SettingsDialog(QtWidgets.QDialog): # General options layout general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(public_mode_widget) - general_group_layout.addWidget(shutdown_timeout_widget) - general_group_layout.addWidget(use_legacy_v2_onions_widget) - general_group_layout.addWidget(save_private_key_widget) - general_group_layout.addWidget(use_stealth_widget) + general_group_layout.addWidget(self.public_mode_widget) + general_group_layout.addWidget(self.shutdown_timeout_widget) + general_group_layout.addWidget(self.use_legacy_v2_onions_widget) + general_group_layout.addWidget(self.save_private_key_widget) + general_group_layout.addWidget(self.use_stealth_widget) general_group_layout.addWidget(hidservauth_details) general_group_layout.addWidget(self.hidservauth_copy_button) @@ -463,6 +463,13 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + if use_legacy_v2_onions: + self.save_private_key_widget.show() + self.use_stealth_widget.show() + else: + self.save_private_key_widget.hide() + self.use_stealth_widget.hide() + save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) @@ -495,7 +502,7 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Checked) # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) - if save_private_key: + if save_private_key and self.old_settings.get('hidservauth_string') != "": hidservauth_details.show() self.hidservauth_copy_button.show() else: @@ -665,6 +672,17 @@ class SettingsDialog(QtWidgets.QDialog): clipboard = self.qtapp.clipboard() clipboard.setText(self.old_settings.get('hidservauth_string')) + def use_legacy_v2_onions_checkbox_clicked(self, checked): + """ + Show the legacy settings if the legacy mode is enabled. + """ + if checked: + self.save_private_key_widget.show() + self.use_stealth_widget.show() + else: + self.save_private_key_widget.hide() + self.use_stealth_widget.hide() + def save_private_key_checkbox_clicked(self, checked): """ Prevent the v2 legacy mode being switched off if persistence is enabled -- cgit v1.2.3-54-g00ecf From adf4b0298035a3079f78db1b87d31bf356ead59f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:16:11 +1000 Subject: Update stdeb.cfg to depend on bionic and Python 3.6 --- stdeb.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdeb.cfg b/stdeb.cfg index 334502c0..e190fe8b 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -2,5 +2,5 @@ Package3: onionshare Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5 -Suite: xenial -X-Python3-Version: >= 3.4 +Suite: bionic +X-Python3-Version: >= 3.6 -- cgit v1.2.3-54-g00ecf From b06fd8af26682f394ad1f075fce41ce9aeea04a1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 17:42:04 +1000 Subject: Hold a share open if its timer hsa expired but a file is still uploading. Don't allow other uploads during this time --- onionshare/web.py | 176 +++++++++++++++++--------------- onionshare_gui/receive_mode/__init__.py | 11 ++ share/locale/en.json | 1 + 3 files changed, 106 insertions(+), 82 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index 221c2c53..9c3d52ba 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -118,6 +118,7 @@ class Web(object): self.download_in_progress = False self.done = False + self.can_upload = True # If the client closes the OnionShare window while a download is in progress, # it should immediately stop serving the file. The client_cancel global is @@ -343,96 +344,104 @@ class Web(object): """ Upload files. """ - # Make sure downloads_dir exists - valid = True - try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) + while self.can_upload: + # Make sure downloads_dir exists + valid = True + try: + self.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) - self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) + # Register that uploads were sent (for shutdown timer) + self.done = True + + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): self.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) + if self.can_upload: + return upload_logic(slug_candidate) + else: + return self.error404() @self.app.route("/upload", methods=['POST']) def upload_public(): - if not self.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode') or not self.can_upload: return self.error404() return upload_logic() @@ -449,11 +458,14 @@ class Web(object): @self.app.route("//close", methods=['POST']) def close(slug_candidate): self.check_slug_candidate(slug_candidate) - return close_logic(slug_candidate) + if self.can_upload: + return close_logic(slug_candidate) + else: + return self.error404() @self.app.route("/close", methods=['POST']) def close_public(): - if not self.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode') or not self.can_upload: return self.error404() return close_logic() @@ -745,7 +757,7 @@ class ReceiveModeRequest(Request): # Is this a valid upload request? self.upload_request = False - if self.method == 'POST': + if self.method == 'POST' and self.web.can_upload: if self.path == '/{}/upload'.format(self.web.slug): self.upload_request = True else: diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 623d3986..a9f15749 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -98,6 +98,17 @@ class ReceiveMode(Mode): The shutdown timer expired, should we stop the server? Returns a bool """ # TODO: wait until the final upload is done before stoppign the server? + # If there were no attempts to upload files, or all uploads are done, we can stop + if self.web.upload_count == 0 or self.web.done: + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_timeout', True)) + return True + # An upload is probably still running - hold off on stopping the share, but block new shares. + else: + self.server_status_label.setText(strings._('timeout_upload_still_running', True)) + self.web.can_upload = False + return False + return True def start_server_custom(self): diff --git a/share/locale/en.json b/share/locale/en.json index cc39bba7..beebac77 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -15,6 +15,7 @@ "close_on_timeout": "Stopped because timer expired", "closing_automatically": "Stopped because download finished", "timeout_download_still_running": "Waiting for download to complete", + "timeout_upload_still_running": "Waiting for upload to complete", "large_filesize": "Warning: Sending large files could take hours", "systray_menu_exit": "Quit", "systray_download_started_title": "OnionShare Download Started", -- cgit v1.2.3-54-g00ecf From 953727419c8f419940bf0fdad4db26956ba452ee Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 18:48:22 +1000 Subject: Use the term 'upload' rather than 'download' in the Receive mode tooltip icons --- onionshare_gui/receive_mode/__init__.py | 4 ++-- share/locale/en.json | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 623d3986..6a8d3835 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -194,7 +194,7 @@ class ReceiveMode(Mode): else: image = self.common.get_resource_path('images/share_completed.png') self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) - self.info_completed_uploads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.uploads_completed)) + self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed)) def update_uploads_in_progress(self): """ @@ -206,7 +206,7 @@ class ReceiveMode(Mode): image = self.common.get_resource_path('images/share_in_progress.png') self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png'))) self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) - self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.uploads_in_progress)) + self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') diff --git a/share/locale/en.json b/share/locale/en.json index cc39bba7..a3a7f3df 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -155,6 +155,8 @@ "gui_file_info_single": "{} File, {}", "info_in_progress_downloads_tooltip": "{} download(s) in progress", "info_completed_downloads_tooltip": "{} download(s) completed", + "info_in_progress_uploads_tooltip": "{} upload(s) in progress", + "info_completed_uploads_tooltip": "{} upload(s) completed", "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", -- cgit v1.2.3-54-g00ecf From abfc2f1c651acddf616b2ef72cce14e4460734e1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 15:07:19 -0700 Subject: Update Windows deps to python 3.7.0, pywin32 223, and Qt 5.11.1 --- BUILD.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index 7d8350a6..f9c97b46 100644 --- a/BUILD.md +++ b/BUILD.md @@ -77,7 +77,7 @@ Now you should have `dist/OnionShare.pkg`. ### Setting up your dev environment -Download Python 3.6.4, 32-bit (x86) from https://www.python.org/downloads/release/python-364/. I downloaded `python-3.6.4.exe`. When installing it, make sure to check the "Add Python 3.6 to PATH" checkbox on the first page of the installer. +Download Python 3.7.0, 32-bit (x86) from https://www.python.org/downloads/release/python-370/. I downloaded `python-3.7.0.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer. Open a command prompt, cd to the onionshare folder, and install dependencies with pip: @@ -85,9 +85,9 @@ Open a command prompt, cd to the onionshare folder, and install dependencies wit pip3 install -r install\requirements-windows.txt ``` -Download and install pywin32 (build 221, x86, for python 3.6) from https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/. I downloaded `pywin32-221.win32-py3.6.exe`. +Download and install pywin32 (build 223, x86, for python 3.7) from https://github.com/mhammond/pywin32/releases/tag/b223. I downloaded `pywin32-223.win32-py3.7.exe`. -Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.4-online.exe`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.11.0. You only need to install the `MSVC 2015 32-bit` component, as well as all of the the `Qt` components, for that that version. +Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.5-online.exe`. There's no need to login to a Qt account during installation. When you can select components, install the `MSVC 2015 32-bit` component from Qt 5.11.1 (or whatever the latest Qt version is). After that you can try both the CLI and the GUI version of OnionShare: -- cgit v1.2.3-54-g00ecf From 711a7b84d7d38c518935e4f6419d58fa13de1f5f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 15:15:16 -0700 Subject: Get tor binary from Tor Browser 8.0 --- install/get-tor-osx.py | 2 +- install/get-tor-windows.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 3c498dfe..5b8078c5 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -37,7 +37,7 @@ import requests def main(): dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/TorBrowser-7.5.5-osx64_en-US.dmg' dmg_filename = 'TorBrowser-7.5.5-osx64_en-US.dmg' - expected_dmg_sha256 = '2b445e4237cdd9be0e71e65f76db5d36f0d6c37532982d642803b57e388e4636' + expected_dmg_sha256 = '15603ae7b3a1942863c98acc92f509e4409db48fe22c9acae6b15c9cb9bf3088' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 44c4ac23..67e41362 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -33,9 +33,9 @@ import subprocess import requests def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/torbrowser-install-7.5.5_en-US.exe' - exe_filename = 'torbrowser-install-7.5.5_en-US.exe' - expected_exe_sha256 = '992f9a6658001c3419ed3695a908eef4fb7feb1cd549389bdacbadb7f8cb08a7' + exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/torbrowser-install-8.0_en-US.exe' + exe_filename = 'torbrowser-install-8.0_en-US.exe' + expected_exe_sha256 = '0682b44eff5877dfc2fe2fdd5b46e678d47adad86d564e7cb6654c5f60eb1ed2' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') -- cgit v1.2.3-54-g00ecf From 7e875e021a34bc63ddee8d48b32c918d9561f7c0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 08:35:58 +1000 Subject: Remove unnecessary loop. Remove the Close route/setting which can DoS another running upload. Fix detecting whether any uploads are still in progress before terminating the service after timer expires. Don't register 404s for uploads after expiry has finished (throw a 403 instead)" --- onionshare/settings.py | 3 +- onionshare/web.py | 208 +++++++++++++++----------------- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/settings_dialog.py | 13 -- share/locale/en.json | 1 - share/templates/403.html | 16 +++ share/templates/receive.html | 9 -- test/test_onionshare_settings.py | 3 +- test/test_onionshare_web.py | 30 ----- 9 files changed, 114 insertions(+), 171 deletions(-) create mode 100644 share/templates/403.html diff --git a/onionshare/settings.py b/onionshare/settings.py index ef75be2f..6cc70954 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -72,8 +72,7 @@ class Settings(object): 'public_mode': False, 'slug': '', 'hidservauth_string': '', - 'downloads_dir': self.build_default_downloads_dir(), - 'receive_allow_receiver_shutdown': True + 'downloads_dir': self.build_default_downloads_dir() } self._settings = {} self.fill_in_defaults() diff --git a/onionshare/web.py b/onionshare/web.py index 9c3d52ba..c197caf1 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -119,6 +119,7 @@ class Web(object): self.done = False self.can_upload = True + self.uploads_in_progress = [] # If the client closes the OnionShare window while a download is in progress, # it should immediately stop serving the file. The client_cancel global is @@ -323,9 +324,7 @@ class Web(object): r = make_response(render_template( 'receive.html', - upload_action=upload_action, - close_action=close_action, - receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) + upload_action=upload_action)) return self.add_security_headers(r) @self.app.route("/") @@ -344,131 +343,103 @@ class Web(object): """ Upload files. """ - while self.can_upload: - # Make sure downloads_dir exists - valid = True - try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) - - self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - - - # Register that uploads were sent (for shutdown timer) - self.done = True - + # Make sure downloads_dir exists + valid = True + try: + self.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') if self.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') + + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + @self.app.route("//upload", methods=['POST']) def upload(slug_candidate): + if not self.can_upload: + return self.error403() self.check_slug_candidate(slug_candidate) - if self.can_upload: - return upload_logic(slug_candidate) - else: - return self.error404() + return upload_logic(slug_candidate) @self.app.route("/upload", methods=['POST']) def upload_public(): - if not self.common.settings.get('public_mode') or not self.can_upload: + if not self.common.settings.get('public_mode'): return self.error404() + if not self.can_upload: + return self.error403() return upload_logic() - def close_logic(slug_candidate=''): - if self.common.settings.get('receive_allow_receiver_shutdown'): - self.force_shutdown() - r = make_response(render_template('closed.html')) - self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) - return self.add_security_headers(r) - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//close", methods=['POST']) - def close(slug_candidate): - self.check_slug_candidate(slug_candidate) - if self.can_upload: - return close_logic(slug_candidate) - else: - return self.error404() - - @self.app.route("/close", methods=['POST']) - def close_public(): - if not self.common.settings.get('public_mode') or not self.can_upload: - return self.error404() - return close_logic() - def common_routes(self): """ Common web app routes between sending and receiving @@ -504,6 +475,12 @@ class Web(object): r = make_response(render_template('404.html'), 404) return self.add_security_headers(r) + def error403(self): + self.add_request(Web.REQUEST_OTHER, request.path) + + r = make_response(render_template('403.html'), 403) + return self.add_security_headers(r) + def add_security_headers(self, r): """ Add security headers to a request @@ -783,6 +760,8 @@ class ReceiveModeRequest(Request): datetime.now().strftime("%b %d, %I:%M%p"), strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) )) + # append to self.uploads_in_progress + self.web.uploads_in_progress.append(self.upload_id) # Tell the GUI self.web.add_request(Web.REQUEST_STARTED, self.path, { @@ -816,6 +795,9 @@ class ReceiveModeRequest(Request): 'id': self.upload_id }) + # remove from self.uploads_in_progress + self.web.uploads_in_progress.remove(self.upload_id) + def file_write_func(self, filename, length): """ This function gets called when a specific file is written to. diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index a9f15749..8c360d19 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -99,7 +99,7 @@ class ReceiveMode(Mode): """ # TODO: wait until the final upload is done before stoppign the server? # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.upload_count == 0 or self.web.done: + if self.web.upload_count == 0 or not self.web.uploads_in_progress: self.server_status.stop_server() self.server_status_label.setText(strings._('close_on_timeout', True)) return True diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index c4e6b752..c23f8ad8 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -101,15 +101,9 @@ class SettingsDialog(QtWidgets.QDialog): downloads_layout.addWidget(self.downloads_dir_lineedit) downloads_layout.addWidget(downloads_button) - # Allow the receiver to shutdown the server - self.receive_allow_receiver_shutdown_checkbox = QtWidgets.QCheckBox() - 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)) - # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout.addLayout(downloads_layout) - receiving_group_layout.addWidget(self.receive_allow_receiver_shutdown_checkbox) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label", True)) receiving_group.setLayout(receiving_group_layout) @@ -421,12 +415,6 @@ class SettingsDialog(QtWidgets.QDialog): downloads_dir = self.old_settings.get('downloads_dir') self.downloads_dir_lineedit.setText(downloads_dir) - receive_allow_receiver_shutdown = self.old_settings.get('receive_allow_receiver_shutdown') - if receive_allow_receiver_shutdown: - self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Checked) - else: - self.receive_allow_receiver_shutdown_checkbox.setCheckState(QtCore.Qt.Unchecked) - public_mode = self.old_settings.get('public_mode') if public_mode: self.public_mode_checkbox.setCheckState(QtCore.Qt.Checked) @@ -802,7 +790,6 @@ class SettingsDialog(QtWidgets.QDialog): # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') settings.set('downloads_dir', self.downloads_dir_lineedit.text()) - settings.set('receive_allow_receiver_shutdown', self.receive_allow_receiver_shutdown_checkbox.isChecked()) settings.set('public_mode', self.public_mode_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset diff --git a/share/locale/en.json b/share/locale/en.json index beebac77..98addae9 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -168,7 +168,6 @@ "gui_settings_receiving_label": "Receiving options", "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_public_mode_checkbox": "OnionShare is open to the public\n(don't prevent people from guessing the OnionShare address)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", diff --git a/share/templates/403.html b/share/templates/403.html new file mode 100644 index 00000000..df81f3e7 --- /dev/null +++ b/share/templates/403.html @@ -0,0 +1,16 @@ + + + + OnionShare: 403 Forbidden + + + + +
    +
    +

    +

    You are not allowed to perform that action at this time.

    +
    +
    + + diff --git a/share/templates/receive.html b/share/templates/receive.html index d8b02f73..e85b6ff9 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -34,14 +34,5 @@
    - {% if receive_allow_receiver_shutdown %} - {% with messages = get_flashed_messages() %} - {% if messages %} -
    - -
    - {% endif %} - {% endwith %} - {% endif %} diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index 1521492d..fb9e1f69 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -63,8 +63,7 @@ class TestSettings: 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/OnionShare'), - 'receive_allow_receiver_shutdown': True, + 'downloads_dir': os.path.expanduser('~/OnionShare') 'public_mode': False } diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 2209a0fd..d523ea96 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -138,36 +138,6 @@ class TestWeb: res.get_data() assert res.status_code == 200 - def test_receive_mode_allow_receiver_shutdown_on(self, common_obj): - web = web_obj(common_obj, True) - - common_obj.settings.set('receive_allow_receiver_shutdown', True) - - assert web.running == True - - with web.app.test_client() as c: - # Load close page - res = c.post('/{}/close'.format(web.slug)) - res.get_data() - # Should return ok, and server should stop - assert res.status_code == 200 - assert web.running == False - - def test_receive_mode_allow_receiver_shutdown_off(self, common_obj): - web = web_obj(common_obj, True) - - common_obj.settings.set('receive_allow_receiver_shutdown', False) - - assert web.running == True - - with web.app.test_client() as c: - # Load close page - res = c.post('/{}/close'.format(web.slug)) - res.get_data() - # Should redirect to index, and server should still be running - assert res.status_code == 302 - assert web.running == True - def test_public_mode_on(self, common_obj): web = web_obj(common_obj, True) common_obj.settings.set('public_mode', True) -- cgit v1.2.3-54-g00ecf From 92ef6f71392183c6cc74ffe20590745da883881c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 15:37:48 -0700 Subject: Update Mac deps to python 3.7.0 and Qt 5.11.1 --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index f9c97b46..2cae8f20 100644 --- a/BUILD.md +++ b/BUILD.md @@ -38,11 +38,11 @@ If you find that these instructions don't work for your Linux distribution or ve Install Xcode from the Mac App Store. Once it's installed, run it for the first time to set it up. Also, run this to make sure command line tools are installed: `xcode-select --install`. And finally, open Xcode, go to Preferences > Locations, and make sure under Command Line Tools you select an installed version from the dropdown. (This is required for installing Qt5.) -Download and install Python 3.6.4 from https://www.python.org/downloads/release/python-364/. I downloaded `python-3.6.4-macosx10.6.pkg`. +Download and install Python 3.7.0 from https://www.python.org/downloads/release/python-370/. I downloaded `python-3.7.0-macosx10.9.pkg`. You may also need to run the command `/Applications/Python\ 3.6/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error. -Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.2-online.dmg`. There's no need to login to a Qt account during installation. Make sure you install the latest Qt 5.x. I installed Qt 5.10.0 -- all you need is to check `Qt > Qt 5.10.0 > macOS`. +Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.5-online.dmg`. There's no need to login to a Qt account during installation. When you select components, install the `macOS` component from Qt 5.11.1 (or whatever the latest Qt version is). Now install some python dependencies with pip (note, there's issues building a .app if you install this in a virtualenv): -- cgit v1.2.3-54-g00ecf From eb66aceecafe696ee24783ade3bba2c61c764468 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 15:54:37 -0700 Subject: Update travis config to use python 3.6+, and to install proper versions of dependencies, and to use bionic instead of trusty --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9010e77a..afbaa887 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,15 @@ language: python # sudo: required -dist: trusty +dist: bionic python: - - "3.4" - - "3.5" - "3.6" - "3.6-dev" - "3.7-dev" - "nightly" # command to install dependencies install: - - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8 + - pip install -r install/requirements.txt + - pip install pytest-cov coveralls flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From 359e4703833a66eaa9ca7f83ead8053346e9c1a3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 16:11:52 -0700 Subject: Make what's this links use the same string, and change their style --- onionshare/common.py | 5 +++++ onionshare_gui/settings_dialog.py | 15 ++++++++++----- share/locale/en.json | 6 +----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 61663f23..0ce411e8 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -346,6 +346,11 @@ class Common(object): background-color: #ffffff; color: #000000; padding: 10px; + }""", + + 'settings_whats_this': """ + QLabel { + font-size: 12px; }""" } diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 895cde13..c31d4630 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -58,7 +58,8 @@ class SettingsDialog(QtWidgets.QDialog): self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_public_mode_details", True)) + public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) + public_mode_label.setStyleSheet(self.common.css['settings_whats_this']) public_mode_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) public_mode_label.setMinimumSize(public_mode_label.sizeHint()) @@ -74,7 +75,8 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_shutdown_timeout_details", True)) + 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.setStyleSheet(self.common.css['settings_whats_this']) shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) shutdown_timeout_label.setOpenExternalLinks(True) shutdown_timeout_label.setMinimumSize(public_mode_label.sizeHint()) @@ -91,7 +93,8 @@ class SettingsDialog(QtWidgets.QDialog): 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.clicked.connect(self.use_legacy_v2_onions_checkbox_clicked) - use_legacy_v2_onions_label = QtWidgets.QLabel(strings._("gui_use_legacy_v2_onions_label", True)) + 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.setStyleSheet(self.common.css['settings_whats_this']) use_legacy_v2_onions_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_legacy_v2_onions_label.setOpenExternalLinks(True) use_legacy_v2_onions_layout = QtWidgets.QHBoxLayout() @@ -107,7 +110,8 @@ class SettingsDialog(QtWidgets.QDialog): 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.clicked.connect(self.save_private_key_checkbox_clicked) - save_private_key_label = QtWidgets.QLabel(strings._("gui_save_private_key_label", True)) + 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.setStyleSheet(self.common.css['settings_whats_this']) save_private_key_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) save_private_key_layout = QtWidgets.QHBoxLayout() @@ -123,7 +127,8 @@ class SettingsDialog(QtWidgets.QDialog): self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.clicked.connect(self.stealth_checkbox_clicked_connect) - use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_stealth_option_details", True)) + use_stealth_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Stealth-Onion-Services")) + use_stealth_label.setStyleSheet(self.common.css['settings_whats_this']) use_stealth_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) use_stealth_label.setMinimumSize(use_stealth_label.sizeHint()) diff --git a/share/locale/en.json b/share/locale/en.json index c70ca2eb..4e7143d3 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -71,8 +71,8 @@ "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", "gui_settings_window_title": "Settings", + "gui_settings_whats_this": "what's this?", "gui_settings_stealth_option": "Create stealth onion services (legacy)", - "gui_settings_stealth_option_details": "(what's this?)", "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.", "gui_settings_autoupdate_label": "Check for upgrades", "gui_settings_autoupdate_option": "Notify me when upgrades are available", @@ -109,7 +109,6 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", - "gui_settings_shutdown_timeout_details": "(what's this?)", "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved to {}", "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", @@ -140,9 +139,7 @@ "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "share_via_onionshare": "Share via OnionShare", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", - "gui_use_legacy_v2_onions_label": "(what's this?)", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", - "gui_save_private_key_label": "(what's this?)", "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", @@ -175,7 +172,6 @@ "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_public_mode_checkbox": "Public mode", - "gui_settings_public_mode_details": "(what's this?)", "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", -- cgit v1.2.3-54-g00ecf From 4434e4c920428f2487f9f2d4658d1ff1f8835087 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 09:59:06 +1000 Subject: Fix test --- test/test_onionshare_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py index fb9e1f69..937288d1 100644 --- a/test/test_onionshare_settings.py +++ b/test/test_onionshare_settings.py @@ -63,7 +63,7 @@ class TestSettings: 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/OnionShare') + 'downloads_dir': os.path.expanduser('~/OnionShare'), 'public_mode': False } -- cgit v1.2.3-54-g00ecf From 7c5d1545193c4a2a09cfee29c751f31b3cbcd1d1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 17:42:21 -0700 Subject: Make separate function for comparing the slug and comparing the shutdown_slug, to prevent 404 errors on the shutdown request --- onionshare/web.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index e3e965da..10c130cb 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -483,7 +483,7 @@ class Web(object): """ Stop the flask web server, from the context of an http request. """ - self.check_slug_candidate(slug_candidate, self.shutdown_slug) + self.check_shutdown_slug_candidate(slug_candidate) self.force_shutdown() return "" @@ -578,15 +578,17 @@ class Web(object): log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) - def check_slug_candidate(self, slug_candidate, slug_compare=None): - self.common.log('Web', 'check_slug_candidate: slug_candidate={}, slug_compare={}'.format(slug_candidate, slug_compare)) + def check_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) if self.common.settings.get('public_mode'): abort(404) - else: - if not slug_compare: - slug_compare = self.slug - if not hmac.compare_digest(slug_compare, slug_candidate): - abort(404) + if not hmac.compare_digest(self.slug, slug_candidate): + abort(404) + + def check_shutdown_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) + if not hmac.compare_digest(self.shutdown_slug, slug_candidate): + abort(404) def force_shutdown(self): """ -- cgit v1.2.3-54-g00ecf From c8fd40c3b0adfc3d357539fc8e3a603806a754ae Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 11:19:20 +1000 Subject: Increment/decrement the upload counters --- onionshare_gui/receive_mode/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 6a8d3835..d414f3b0 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -166,6 +166,12 @@ class ReceiveMode(Mode): Handle REQUEST_UPLOAD_FINISHED event. """ self.uploads.finished(event["data"]["id"]) + # Update the total 'completed uploads' info + self.uploads_completed += 1 + self.update_uploads_completed() + # Update the 'in progress uploads' info + self.uploads_in_progress -= 1 + self.update_uploads_in_progress() def on_reload_settings(self): """ @@ -187,7 +193,7 @@ class ReceiveMode(Mode): def update_uploads_completed(self): """ - Update the 'Downloads completed' info widget. + Update the 'Uploads completed' info widget. """ if self.uploads_completed == 0: image = self.common.get_resource_path('images/share_completed_none.png') @@ -198,7 +204,7 @@ class ReceiveMode(Mode): def update_uploads_in_progress(self): """ - Update the 'Downloads in progress' info widget. + Update the 'Uploads in progress' info widget. """ if self.uploads_in_progress == 0: image = self.common.get_resource_path('images/share_in_progress_none.png') -- cgit v1.2.3-54-g00ecf From bf6de202b012df156335439452caa718294cb6d4 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 11:51:32 +1000 Subject: Close the upload widget on reset so that it properly disappears from the Uploads window. --- onionshare_gui/receive_mode/uploads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 09834156..e928bd62 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -290,6 +290,7 @@ class Uploads(QtWidgets.QScrollArea): """ self.common.log('Uploads', 'reset') for upload in self.uploads.values(): + upload.close() self.uploads_layout.removeWidget(upload) self.uploads = {} -- cgit v1.2.3-54-g00ecf From d54b52691cf3cd6acd1298c0ee73949299f7b7b0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 12:59:01 +1000 Subject: Fix the auto-scrolling to bottom of Download and Upload windows --- onionshare_gui/receive_mode/uploads.py | 10 +++++++--- onionshare_gui/share_mode/downloads.py | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 09834156..1aebe902 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -227,6 +227,7 @@ class Uploads(QtWidgets.QScrollArea): 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', True)) uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) @@ -243,6 +244,12 @@ class Uploads(QtWidgets.QScrollArea): 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. @@ -256,9 +263,6 @@ class Uploads(QtWidgets.QScrollArea): self.uploads[upload_id] = upload self.uploads_layout.addWidget(upload) - # Scroll to the bottom - self.vbar.setValue(self.vbar.maximum()) - def update(self, upload_id, progress): """ Update the progress of an upload. diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index f5e8512e..538ddfd0 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -97,6 +97,7 @@ class Downloads(QtWidgets.QScrollArea): 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', True)) downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) @@ -113,6 +114,12 @@ class Downloads(QtWidgets.QScrollArea): 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. @@ -125,9 +132,6 @@ class Downloads(QtWidgets.QScrollArea): self.downloads[download_id] = download self.downloads_layout.addWidget(download.progress_bar) - # Scroll to the bottom - self.vbar.setValue(self.vbar.maximum()) - def update(self, download_id, downloaded_bytes): """ Update the progress of a download progress bar. -- cgit v1.2.3-54-g00ecf From 174de57405ba176a873e1cc1cb08732dd7d207f9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 20:55:54 -0700 Subject: Refactor all of the threading.Threads into QThreads, and quit them all when canceling the server. When canceling the compression thread, specifically mass a cancel message into the Web and ZipWriter objects to make the bail out on compression early --- onionshare/web.py | 25 +++++++++--- onionshare_gui/mode.py | 55 +++++++++++--------------- onionshare_gui/onion_thread.py | 45 --------------------- onionshare_gui/share_mode/__init__.py | 43 ++++++++++---------- onionshare_gui/share_mode/threads.py | 60 ++++++++++++++++++++++++++++ onionshare_gui/threads.py | 74 +++++++++++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 106 deletions(-) delete mode 100644 onionshare_gui/onion_thread.py create mode 100644 onionshare_gui/share_mode/threads.py create mode 100644 onionshare_gui/threads.py diff --git a/onionshare/web.py b/onionshare/web.py index 10c130cb..e24e4665 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -104,6 +104,8 @@ class Web(object): self.file_info = [] self.zip_filename = None self.zip_filesize = None + self.zip_writer = None + self.cancel_compression = False self.security_headers = [ ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), @@ -534,14 +536,20 @@ class Web(object): self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) - # zip up the files and folders - z = ZipWriter(self.common, processed_size_callback=processed_size_callback) + # Zip up the files and folders + self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.zip_filename = self.zip_writer.zip_filename for info in self.file_info['files']: - z.add_file(info['filename']) + self.zip_writer.add_file(info['filename']) + # Canceling early? + if self.cancel_compression: + self.zip_writer.close() + return + for info in self.file_info['dirs']: - z.add_dir(info['filename']) - z.close() - self.zip_filename = z.zip_filename + self.zip_writer.add_dir(info['filename']) + + self.zip_writer.close() self.zip_filesize = os.path.getsize(self.zip_filename) def _safe_select_jinja_autoescape(self, filename): @@ -653,6 +661,7 @@ class ZipWriter(object): """ def __init__(self, common, zip_filename=None, processed_size_callback=None): self.common = common + self.cancel_compression = False if zip_filename: self.zip_filename = zip_filename @@ -681,6 +690,10 @@ class ZipWriter(object): dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' for dirpath, dirnames, filenames in os.walk(filename): for f in filenames: + # Canceling early? + if self.cancel_compression: + return + full_filename = os.path.join(dirpath, f) if not os.path.islink(full_filename): arc_filename = full_filename[len(dir_to_strip):] diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 418afffd..feb2f5b6 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -17,15 +17,13 @@ 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 . """ -import time -import threading from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.common import ShutdownTimer from .server_status import ServerStatus -from .onion_thread import OnionThread +from .threads import OnionThread from .widgets import Alert class Mode(QtWidgets.QWidget): @@ -56,6 +54,10 @@ class Mode(QtWidgets.QWidget): # The web object gets created in init() self.web = None + # Threads start out as None + self.onion_thread = None + self.web_thread = None + # Server status self.server_status = ServerStatus(self.common, self.qtapp, self.app) self.server_status.server_started.connect(self.start_server) @@ -138,34 +140,11 @@ class Mode(QtWidgets.QWidget): self.status_bar.clearMessage() self.server_status_label.setText('') - # Start the onion service in a new thread - def start_onion_service(self): - # Choose a port for the web app - self.app.choose_port() - - # Start http service in new thread - t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('public_mode'), self.common.settings.get('slug'))) - t.daemon = True - t.start() - - # Wait for the web app slug to generate before continuing - if not self.common.settings.get('public_mode'): - while self.web.slug == None: - time.sleep(0.1) - - # Now start the onion service - try: - self.app.start_onion_service() - self.starting_server_step2.emit() - - except Exception as e: - self.starting_server_error.emit(e.args[0]) - return - self.common.log('Mode', 'start_server', 'Starting an onion thread') - self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self}) - self.t.daemon = True - self.t.start() + self.onion_thread = OnionThread(self) + self.onion_thread.success.connect(self.starting_server_step2.emit) + self.onion_thread.error.connect(self.starting_server_error.emit) + self.onion_thread.start() def start_server_custom(self): """ @@ -243,10 +222,22 @@ class Mode(QtWidgets.QWidget): """ Cancel the server while it is preparing to start """ - if self.t: - self.t.quit() + self.cancel_server_custom() + + if self.onion_thread: + self.common.log('Mode', 'cancel_server: quitting onion thread') + self.onion_thread.quit() + if self.web_thread: + self.common.log('Mode', 'cancel_server: quitting web thread') + self.web_thread.quit() self.stop_server() + def cancel_server_custom(self): + """ + Add custom initialization here. + """ + pass + def stop_server(self): """ Stop the onionshare server. diff --git a/onionshare_gui/onion_thread.py b/onionshare_gui/onion_thread.py deleted file mode 100644 index 0a25e891..00000000 --- a/onionshare_gui/onion_thread.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore - -class OnionThread(QtCore.QThread): - """ - A QThread for starting our Onion Service. - By using QThread rather than threading.Thread, we are able - to call quit() or terminate() on the startup if the user - decided to cancel (in which case do not proceed with obtaining - the Onion address and starting the web server). - """ - def __init__(self, common, function, kwargs=None): - super(OnionThread, self).__init__() - - self.common = common - - self.common.log('OnionThread', '__init__') - self.function = function - if not kwargs: - self.kwargs = {} - else: - self.kwargs = kwargs - - def run(self): - self.common.log('OnionThread', 'run') - - self.function(**self.kwargs) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 37315bbe..d43fe99f 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -17,7 +17,6 @@ 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 . """ -import threading import os from PyQt5 import QtCore, QtWidgets, QtGui @@ -28,6 +27,7 @@ from onionshare.web import Web from .file_selection import FileSelection from .downloads import Downloads +from .threads import CompressThread from ..mode import Mode from ..widgets import Alert @@ -39,6 +39,9 @@ class ShareMode(Mode): """ Custom initialization for ReceiveMode. """ + # Threads start out as None + self.compress_thread = None + # Create the Web object self.web = Web(self.common, True, False) @@ -161,28 +164,13 @@ class ShareMode(Mode): self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) self.status_bar.insertWidget(0, self._zip_progress_bar) - # Prepare the files for sending in a new thread - def finish_starting_server(self): - # Prepare files to share - def _set_processed_size(x): - if self._zip_progress_bar != None: - self._zip_progress_bar.update_processed_size_signal.emit(x) - - try: - self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size) - self.app.cleanup_filenames.append(self.web.zip_filename) - - # Only continue if the server hasn't been canceled - if self.server_status.status != self.server_status.STATUS_STOPPED: - self.starting_server_step3.emit() - self.start_server_finished.emit() - except OSError as e: - self.starting_server_error.emit(e.strerror) - return - - t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) - t.daemon = True - t.start() + # prepare the files for sending in a new thread + self.compress_thread = CompressThread(self) + self.compress_thread.success.connect(self.starting_server_step3.emit) + self.compress_thread.success.connect(self.start_server_finished.emit) + self.compress_thread.error.connect(self.starting_server_error.emit) + self.server_status.server_canceled.connect(self.compress_thread.cancel) + self.compress_thread.start() def start_server_step3_custom(self): """ @@ -222,6 +210,15 @@ class ShareMode(Mode): self.update_downloads_in_progress() self.file_selection.file_list.adjustSize() + def cancel_server_custom(self): + """ + Stop the compression thread on cancel + """ + if self.compress_thread: + self.common.log('OnionShareGui', 'cancel_server: quitting compress thread') + self.compress_thread.cancel() + self.compress_thread.quit() + def handle_tor_broke_custom(self): """ Connection to Tor broke. diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py new file mode 100644 index 00000000..50789049 --- /dev/null +++ b/onionshare_gui/share_mode/threads.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore + + +class CompressThread(QtCore.QThread): + """ + Compresses files to be shared + """ + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def __init__(self, mode): + super(CompressThread, self).__init__() + self.mode = mode + self.mode.common.log('CompressThread', '__init__') + + # prepare files to share + def set_processed_size(self, x): + if self.mode._zip_progress_bar != None: + self.mode._zip_progress_bar.update_processed_size_signal.emit(x) + + def run(self): + self.mode.common.log('CompressThread', 'run') + + try: + if self.mode.web.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): + self.success.emit() + else: + # Cancelled + pass + + self.mode.app.cleanup_filenames.append(self.mode.web.zip_filename) + except OSError as e: + self.error.emit(e.strerror) + + def cancel(self): + self.mode.common.log('CompressThread', 'cancel') + + # Let the Web and ZipWriter objects know that we're canceling compression early + self.mode.web.cancel_compression = True + if self.mode.web.zip_writer: + self.mode.web.zip_writer.cancel_compression = True diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py new file mode 100644 index 00000000..3c99d395 --- /dev/null +++ b/onionshare_gui/threads.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import time +from PyQt5 import QtCore + + +class OnionThread(QtCore.QThread): + """ + Starts the onion service, and waits for it to finish + """ + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def __init__(self, mode): + super(OnionThread, self).__init__() + self.mode = mode + self.mode.common.log('OnionThread', '__init__') + + # allow this thread to be terminated + self.setTerminationEnabled() + + def run(self): + self.mode.common.log('OnionThread', 'run') + + try: + self.mode.app.start_onion_service() + self.success.emit() + + except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + self.error.emit(e.args[0]) + return + + self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') + + # start onionshare http service in new thread + self.mode.web_thread = WebThread(self.mode) + self.mode.web_thread.start() + + # wait for modules in thread to load, preventing a thread-related cx_Freeze crash + time.sleep(0.2) + + +class WebThread(QtCore.QThread): + """ + Starts the web service + """ + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def __init__(self, mode): + super(WebThread, self).__init__() + self.mode = mode + self.mode.common.log('WebThread', '__init__') + + def run(self): + self.mode.common.log('WebThread', 'run') + self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('slug')) -- cgit v1.2.3-54-g00ecf From ef71c351c6042c98a8473821cd0fa2518322db3d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 21:02:39 -0700 Subject: Oops, update URL and filename for Tor Browser 8.0 dmg as well as the sha256 checksum --- install/get-tor-osx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 5b8078c5..452fadf3 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -35,8 +35,8 @@ import subprocess import requests def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/7.5.5/TorBrowser-7.5.5-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-7.5.5-osx64_en-US.dmg' + dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/TorBrowser-8.0-osx64_en-US.dmg' + dmg_filename = 'TorBrowser-8.0-osx64_en-US.dmg' expected_dmg_sha256 = '15603ae7b3a1942863c98acc92f509e4409db48fe22c9acae6b15c9cb9bf3088' # Build paths -- cgit v1.2.3-54-g00ecf From 56280d722f625ddc7b0e5a56c299618df266a01c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 15:35:26 +1000 Subject: Re-add the python dependencies necessary for v3 onion support, which got lost in the merge --- install/requirements-windows.txt | 4 ++++ install/requirements.txt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt index 005acc9f..ee593b31 100644 --- a/install/requirements-windows.txt +++ b/install/requirements-windows.txt @@ -2,6 +2,7 @@ altgraph==0.16.1 certifi==2018.8.24 chardet==3.0.4 click==6.7 +cryptography==2.3.1 Flask==1.0.2 future==0.16.0 idna==2.7 @@ -16,6 +17,9 @@ pyparsing==2.2.0 pypiwin32==223 PyQt5==5.11.2 PySocks==1.6.8 +pynacl==1.2.1 +pycrypto==2.6.1 +pysha3==1.0.2 python-dateutil==2.7.3 pywin32==223 requests==2.19.1 diff --git a/install/requirements.txt b/install/requirements.txt index 16179eb7..567b62e5 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -2,6 +2,7 @@ altgraph==0.16.1 certifi==2018.8.24 chardet==3.0.4 click==6.7 +cryptography==2.3.1 Flask==1.0.2 future==0.16.0 idna==2.7 @@ -14,6 +15,9 @@ PyInstaller==3.4 PyQt5==5.11.2 PyQt5-sip==4.19.12 PySocks==1.6.8 +pycrypto==2.6.1 +pynacl==1.2.1 +pysha3==1.0.2 requests==2.19.1 sip==4.19.8 stem==1.6.0 -- cgit v1.2.3-54-g00ecf From a8dc03da370e9dec984d6c1e6bb03047b34433a6 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 18 Sep 2018 15:36:00 +1000 Subject: Update the path to the Tor data from inside the mounted .dmg, and the libevent version name --- install/get-tor-osx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 845f887c..1d2c6f56 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -42,7 +42,7 @@ def main(): # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) working_path = os.path.join(root_path, 'build', 'tor') - dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'TorBrowser.app', 'Contents') + dmg_tor_path = os.path.join('/Volumes', 'Tor Browser', 'Tor Browser.app', 'Contents') dmg_path = os.path.join(working_path, dmg_filename) dist_path = os.path.join(root_path, 'dist', 'OnionShare.app', 'Contents') @@ -88,7 +88,7 @@ def main(): shutil.copyfile(os.path.join(dmg_tor_path, 'Resources', 'TorBrowser', 'Tor', 'geoip6'), os.path.join(dist_path, 'Resources', 'Tor', 'geoip6')) os.chmod(os.path.join(dist_path, 'Resources', 'Tor', 'tor'), 0o755) shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'tor.real'), os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real')) - shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.0.5.dylib')) + shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib'), os.path.join(dist_path, 'MacOS', 'Tor', 'libevent-2.1.6.dylib')) os.chmod(os.path.join(dist_path, 'MacOS', 'Tor', 'tor.real'), 0o755) # obfs4proxy binary shutil.copyfile(os.path.join(dmg_tor_path, 'MacOS', 'Tor', 'PluggableTransports', 'obfs4proxy'), os.path.join(dist_path, 'Resources', 'Tor', 'obfs4proxy')) -- cgit v1.2.3-54-g00ecf From 72f76bf659a0aeba1125f2a3d322300308633055 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 13:39:09 -0700 Subject: We shouldn't call CompressThread.cancel() there because it's already called in a signal --- onionshare_gui/share_mode/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index d43fe99f..65ce1d52 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -216,7 +216,6 @@ class ShareMode(Mode): """ if self.compress_thread: self.common.log('OnionShareGui', 'cancel_server: quitting compress thread') - self.compress_thread.cancel() self.compress_thread.quit() def handle_tor_broke_custom(self): -- cgit v1.2.3-54-g00ecf From c52c846227d892e0a68d15c3db7d09390cb3ada6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 13:42:13 -0700 Subject: Make Web.set_file_info return False on cancel --- onionshare/web.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/onionshare/web.py b/onionshare/web.py index e24e4665..dc0effdb 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -544,13 +544,15 @@ class Web(object): # Canceling early? if self.cancel_compression: self.zip_writer.close() - return + return False for info in self.file_info['dirs']: - self.zip_writer.add_dir(info['filename']) + if not self.zip_writer.add_dir(info['filename']): + return False self.zip_writer.close() self.zip_filesize = os.path.getsize(self.zip_filename) + return True def _safe_select_jinja_autoescape(self, filename): if filename is None: @@ -692,8 +694,8 @@ class ZipWriter(object): for f in filenames: # Canceling early? if self.cancel_compression: - return - + return False + full_filename = os.path.join(dirpath, f) if not os.path.islink(full_filename): arc_filename = full_filename[len(dir_to_strip):] @@ -701,6 +703,8 @@ class ZipWriter(object): self._size += os.path.getsize(full_filename) self.processed_size_callback(self._size) + return True + def close(self): """ Close the zip archive. -- cgit v1.2.3-54-g00ecf From 725434ed974a60a8e563cbcffec3b5bae8794670 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 15:35:10 -0700 Subject: Generated a new requirements.txt by installing the latest version of these pip packages: pip3 install certifi cryptography Flask PyInstaller PyQt5 PySocks pycrypto pynacl requests stem Then running "pip3 freeze". Also, deleted requirements-windows.txt. Mac and Windows can share. --- install/requirements-windows.txt | 30 ------------------------------ install/requirements.txt | 10 ++++++---- 2 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 install/requirements-windows.txt diff --git a/install/requirements-windows.txt b/install/requirements-windows.txt deleted file mode 100644 index ee593b31..00000000 --- a/install/requirements-windows.txt +++ /dev/null @@ -1,30 +0,0 @@ -altgraph==0.16.1 -certifi==2018.8.24 -chardet==3.0.4 -click==6.7 -cryptography==2.3.1 -Flask==1.0.2 -future==0.16.0 -idna==2.7 -itsdangerous==0.24 -Jinja2==2.10 -macholib==1.11 -MarkupSafe==1.0 -packaging==17.1 -pefile==2018.8.8 -PyInstaller==3.4 -pyparsing==2.2.0 -pypiwin32==223 -PyQt5==5.11.2 -PySocks==1.6.8 -pynacl==1.2.1 -pycrypto==2.6.1 -pysha3==1.0.2 -python-dateutil==2.7.3 -pywin32==223 -requests==2.19.1 -sip==4.19.8 -six==1.11.0 -stem==1.6.0 -urllib3==1.23 -Werkzeug==0.14.1 diff --git a/install/requirements.txt b/install/requirements.txt index 567b62e5..32ec6887 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,5 +1,7 @@ altgraph==0.16.1 +asn1crypto==0.24.0 certifi==2018.8.24 +cffi==1.11.5 chardet==3.0.4 click==6.7 cryptography==2.3.1 @@ -11,15 +13,15 @@ Jinja2==2.10 macholib==1.11 MarkupSafe==1.0 pefile==2018.8.8 +pycparser==2.18 +pycryptodome==3.6.6 PyInstaller==3.4 +PyNaCl==1.2.1 PyQt5==5.11.2 PyQt5-sip==4.19.12 PySocks==1.6.8 -pycrypto==2.6.1 -pynacl==1.2.1 -pysha3==1.0.2 requests==2.19.1 -sip==4.19.8 +six==1.11.0 stem==1.6.0 urllib3==1.23 Werkzeug==0.14.1 -- cgit v1.2.3-54-g00ecf From 31fd2ee7b6bb6a5985dc27ade3f84a981f6c9d51 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 15:37:33 -0700 Subject: For Mac build instructions, update a command to use Python 3.7 instead of 3.6, and install pip dependencies without sudo. For Windows, install requirements.txt instead of requirements-windows.txt. Also pywin32 is no longer a dependency, so remove it from build instructions. --- BUILD.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 9d5f73c3..51f5cadd 100644 --- a/BUILD.md +++ b/BUILD.md @@ -42,14 +42,14 @@ Install Xcode from the Mac App Store. Once it's installed, run it for the first Download and install Python 3.7.0 from https://www.python.org/downloads/release/python-370/. I downloaded `python-3.7.0-macosx10.9.pkg`. -You may also need to run the command `/Applications/Python\ 3.6/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error. +You may also need to run the command `/Applications/Python\ 3.7/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error. Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.5-online.dmg`. There's no need to login to a Qt account during installation. When you select components, install the `macOS` component from Qt 5.11.1 (or whatever the latest Qt version is). Now install some python dependencies with pip (note, there's issues building a .app if you install this in a virtualenv): ```sh -sudo pip3 install -r install/requirements.txt +pip3 install -r install/requirements.txt ``` You can run both the CLI and GUI versions of OnionShare without building an bundle: @@ -84,11 +84,9 @@ Download Python 3.7.0, 32-bit (x86) from https://www.python.org/downloads/releas Open a command prompt, cd to the onionshare folder, and install dependencies with pip: ```cmd -pip3 install -r install\requirements-windows.txt +pip install -r install\requirements.txt ``` -Download and install pywin32 (build 223, x86, for python 3.7) from https://github.com/mhammond/pywin32/releases/tag/b223. I downloaded `pywin32-223.win32-py3.7.exe`. - Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.5-online.exe`. There's no need to login to a Qt account during installation. When you can select components, install the `MSVC 2015 32-bit` component from Qt 5.11.1 (or whatever the latest Qt version is). After that you can try both the CLI and the GUI version of OnionShare: -- cgit v1.2.3-54-g00ecf From 814897e9bba8d97c51a0c3800cf2fd8f0db72255 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 16:05:54 -0700 Subject: Update NSIS file to no longer have to specify each individual file (omg I should have done this foreever ago) --- install/onionshare.nsi | 394 +------------------------------------------------ 1 file changed, 2 insertions(+), 392 deletions(-) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 134ff8d2..f0b28535 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -59,203 +59,7 @@ FunctionEnd Section "install" SetOutPath "$INSTDIR" File "onionshare.ico" - File "${BINPATH}\api-ms-win-core-console-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-datetime-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-debug-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-errorhandling-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-file-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-file-l1-2-0.dll" - File "${BINPATH}\api-ms-win-core-file-l2-1-0.dll" - File "${BINPATH}\api-ms-win-core-handle-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-heap-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-interlocked-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-libraryloader-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-localization-l1-2-0.dll" - File "${BINPATH}\api-ms-win-core-memory-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-namedpipe-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-processenvironment-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-processthreads-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-processthreads-l1-1-1.dll" - File "${BINPATH}\api-ms-win-core-profile-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-rtlsupport-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-string-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-synch-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-synch-l1-2-0.dll" - File "${BINPATH}\api-ms-win-core-sysinfo-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-timezone-l1-1-0.dll" - File "${BINPATH}\api-ms-win-core-util-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-conio-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-convert-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-environment-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-filesystem-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-heap-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-locale-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-math-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-multibyte-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-process-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-runtime-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-stdio-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-string-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-time-l1-1-0.dll" - File "${BINPATH}\api-ms-win-crt-utility-l1-1-0.dll" - File "${BINPATH}\base_library.zip" - File "${BINPATH}\mfc140u.dll" - File "${BINPATH}\MSVCP140.dll" - File "${BINPATH}\onionshare-gui.exe" - File "${BINPATH}\onionshare-gui.exe.manifest" - File "${BINPATH}\pyexpat.pyd" - File "${BINPATH}\PyQt5.Qt.pyd" - File "${BINPATH}\PyQt5.QtCore.pyd" - File "${BINPATH}\PyQt5.QtGui.pyd" - File "${BINPATH}\PyQt5.QtPrintSupport.pyd" - File "${BINPATH}\PyQt5.QtWidgets.pyd" - File "${BINPATH}\python3.dll" - File "${BINPATH}\python36.dll" - File "${BINPATH}\pythoncom36.dll" - File "${BINPATH}\pywintypes36.dll" - File "${BINPATH}\Qt5Core.dll" - File "${BINPATH}\Qt5Gui.dll" - File "${BINPATH}\Qt5PrintSupport.dll" - File "${BINPATH}\Qt5Svg.dll" - File "${BINPATH}\Qt5Widgets.dll" - File "${BINPATH}\select.pyd" - File "${BINPATH}\sip.pyd" - File "${BINPATH}\ucrtbase.dll" - File "${BINPATH}\unicodedata.pyd" - File "${BINPATH}\VCRUNTIME140.dll" - File "${BINPATH}\win32api.pyd" - File "${BINPATH}\win32com.shell.shell.pyd" - File "${BINPATH}\win32trace.pyd" - File "${BINPATH}\win32ui.pyd" - File "${BINPATH}\win32wnet.pyd" - File "${BINPATH}\_asyncio.pyd" - File "${BINPATH}\_bz2.pyd" - File "${BINPATH}\_ctypes.pyd" - File "${BINPATH}\_decimal.pyd" - File "${BINPATH}\_hashlib.pyd" - File "${BINPATH}\_lzma.pyd" - File "${BINPATH}\_multiprocessing.pyd" - File "${BINPATH}\_overlapped.pyd" - File "${BINPATH}\_socket.pyd" - File "${BINPATH}\_ssl.pyd" - File "${BINPATH}\_win32sysloader.pyd" - - SetOutPath "$INSTDIR\Include" - File "${BINPATH}\Include\pyconfig.h" - - SetOutPath "$INSTDIR\lib2to3" - File "${BINPATH}\lib2to3\Grammar.txt" - File "${BINPATH}\lib2to3\Grammar3.6.2.candidate.2.pickle" - File "${BINPATH}\lib2to3\Grammar3.6.2.final.0.pickle" - File "${BINPATH}\lib2to3\Grammar3.6.3.candidate.1.pickle" - File "${BINPATH}\lib2to3\Grammar3.6.3.final.0.pickle" - File "${BINPATH}\lib2to3\Grammar3.6.4.candidate.1.pickle" - File "${BINPATH}\lib2to3\Grammar3.6.4.final.0.pickle" - File "${BINPATH}\lib2to3\PatternGrammar.txt" - File "${BINPATH}\lib2to3\PatternGrammar3.6.2.candidate.2.pickle" - File "${BINPATH}\lib2to3\PatternGrammar3.6.2.final.0.pickle" - File "${BINPATH}\lib2to3\PatternGrammar3.6.3.candidate.1.pickle" - File "${BINPATH}\lib2to3\PatternGrammar3.6.3.final.0.pickle" - File "${BINPATH}\lib2to3\PatternGrammar3.6.4.candidate.1.pickle" - File "${BINPATH}\lib2to3\PatternGrammar3.6.4.final.0.pickle" - - SetOutPath "$INSTDIR\lib2to3\tests\data" - File "${BINPATH}\lib2to3\tests\data\README" - - SetOutPath "$INSTDIR\licenses" - File "${BINPATH}\licenses\license-obfs4.txt" - File "${BINPATH}\licenses\license-onionshare.txt" - File "${BINPATH}\licenses\license-tor.txt" - File "${BINPATH}\licenses\readme.txt" - - SetOutPath "$INSTDIR\PyQt5\Qt\bin" - File "${BINPATH}\PyQt5\Qt\bin\qt.conf" - - SetOutPath "$INSTDIR\PyQt5\Qt\plugins\iconengines" - File "${BINPATH}\PyQt5\Qt\plugins\iconengines\qsvgicon.dll" - - SetOutPath "$INSTDIR\PyQt5\Qt\plugins\imageformats" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qgif.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qicns.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qico.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qjpeg.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qsvg.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qtga.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qtiff.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qwbmp.dll" - File "${BINPATH}\PyQt5\Qt\plugins\imageformats\qwebp.dll" - - SetOutPath "$INSTDIR\PyQt5\Qt\plugins\platforms" - File "${BINPATH}\PyQt5\Qt\plugins\platforms\qminimal.dll" - File "${BINPATH}\PyQt5\Qt\plugins\platforms\qoffscreen.dll" - File "${BINPATH}\PyQt5\Qt\plugins\platforms\qwindows.dll" - - SetOutPath "$INSTDIR\PyQt5\Qt\plugins\printsupport" - File "${BINPATH}\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll" - - SetOutPath "$INSTDIR\share" - File "${BINPATH}\share\torrc_template" - File "${BINPATH}\share\torrc_template-windows" - File "${BINPATH}\share\torrc_template-obfs4" - File "${BINPATH}\share\torrc_template-meek_lite_azure" - File "${BINPATH}\share\version.txt" - File "${BINPATH}\share\wordlist.txt" - - SetOutPath "$INSTDIR\share\html" - File "${BINPATH}\share\html\404.html" - File "${BINPATH}\share\html\denied.html" - File "${BINPATH}\share\html\index.html" - - SetOutPath "$INSTDIR\share\images" - File "${BINPATH}\share\images\download_completed.png" - File "${BINPATH}\share\images\download_completed_none.png" - File "${BINPATH}\share\images\download_in_progress.png" - File "${BINPATH}\share\images\download_in_progress_none.png" - File "${BINPATH}\share\images\download_window_gray.png" - File "${BINPATH}\share\images\download_window_green.png" - File "${BINPATH}\share\images\favicon.ico" - File "${BINPATH}\share\images\file_delete.png" - File "${BINPATH}\share\images\info.png" - File "${BINPATH}\share\images\logo.png" - File "${BINPATH}\share\images\logo_transparent.png" - File "${BINPATH}\share\images\logo_grayscale.png" - File "${BINPATH}\share\images\server_started.png" - File "${BINPATH}\share\images\server_stopped.png" - File "${BINPATH}\share\images\server_working.png" - File "${BINPATH}\share\images\settings.png" - File "${BINPATH}\share\images\web_file.png" - File "${BINPATH}\share\images\web_folder.png" - - SetOutPath "$INSTDIR\share\locale" - File "${BINPATH}\share\locale\cs.json" - File "${BINPATH}\share\locale\de.json" - File "${BINPATH}\share\locale\en.json" - File "${BINPATH}\share\locale\eo.json" - File "${BINPATH}\share\locale\es.json" - File "${BINPATH}\share\locale\fi.json" - File "${BINPATH}\share\locale\fr.json" - File "${BINPATH}\share\locale\it.json" - File "${BINPATH}\share\locale\nl.json" - File "${BINPATH}\share\locale\no.json" - File "${BINPATH}\share\locale\pt.json" - File "${BINPATH}\share\locale\ru.json" - File "${BINPATH}\share\locale\tr.json" - - SetOutPath "$INSTDIR\tor\Data\Tor" - File "${BINPATH}\tor\Data\Tor\geoip" - File "${BINPATH}\tor\Data\Tor\geoip6" - - SetOutPath "$INSTDIR\tor\Tor" - File "${BINPATH}\tor\Tor\libeay32.dll" - File "${BINPATH}\tor\Tor\libevent-2-0-5.dll" - File "${BINPATH}\tor\Tor\libevent_core-2-0-5.dll" - File "${BINPATH}\tor\Tor\libevent_extra-2-0-5.dll" - File "${BINPATH}\tor\Tor\libgcc_s_sjlj-1.dll" - File "${BINPATH}\tor\Tor\libssp-0.dll" - File "${BINPATH}\tor\Tor\obfs4proxy.exe" - File "${BINPATH}\tor\Tor\ssleay32.dll" - File "${BINPATH}\tor\Tor\tor.exe" - File "${BINPATH}\tor\Tor\zlib1.dll" + File /a /r "${BINPATH}\" # uninstaller !ifndef INNER @@ -299,201 +103,7 @@ FunctionEnd Delete "$SMPROGRAMS\${APPNAME}.lnk" # remove files - Delete "$INSTDIR\api-ms-win-core-console-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-datetime-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-debug-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-errorhandling-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-file-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-file-l1-2-0.dll" - Delete "$INSTDIR\api-ms-win-core-file-l2-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-handle-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-heap-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-interlocked-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-libraryloader-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-localization-l1-2-0.dll" - Delete "$INSTDIR\api-ms-win-core-memory-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-namedpipe-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-processenvironment-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-processthreads-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-processthreads-l1-1-1.dll" - Delete "$INSTDIR\api-ms-win-core-profile-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-rtlsupport-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-string-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-synch-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-synch-l1-2-0.dll" - Delete "$INSTDIR\api-ms-win-core-sysinfo-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-timezone-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-core-util-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-conio-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-convert-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-environment-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-filesystem-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-heap-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-locale-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-math-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-multibyte-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-process-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-runtime-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-stdio-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-string-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-time-l1-1-0.dll" - Delete "$INSTDIR\api-ms-win-crt-utility-l1-1-0.dll" - Delete "$INSTDIR\base_library.zip" - Delete "$INSTDIR\Include\pyconfig.h" - Delete "$INSTDIR\lib2to3\Grammar.txt" - Delete "$INSTDIR\lib2to3\Grammar3.6.2.candidate.2.pickle" - Delete "$INSTDIR\lib2to3\Grammar3.6.2.final.0.pickle" - Delete "$INSTDIR\lib2to3\Grammar3.6.3.candidate.1.pickle" - Delete "$INSTDIR\lib2to3\Grammar3.6.3.final.0.pickle" - Delete "$INSTDIR\lib2to3\Grammar3.6.4.candidate.1.pickle" - Delete "$INSTDIR\lib2to3\Grammar3.6.4.final.0.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar.txt" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.2.candidate.2.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.2.final.0.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.3.candidate.1.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.3.final.0.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.4.candidate.1.pickle" - Delete "$INSTDIR\lib2to3\PatternGrammar3.6.4.final.0.pickle" - Delete "$INSTDIR\lib2to3\tests" - Delete "$INSTDIR\lib2to3\tests\data" - Delete "$INSTDIR\lib2to3\tests\data\README" - Delete "$INSTDIR\licenses\license-obfs4.txt" - Delete "$INSTDIR\licenses\license-onionshare.txt" - Delete "$INSTDIR\licenses\license-tor.txt" - Delete "$INSTDIR\licenses\readme.txt" - Delete "$INSTDIR\mfc140u.dll" - Delete "$INSTDIR\MSVCP140.dll" - Delete "$INSTDIR\onionshare-gui.exe" - Delete "$INSTDIR\onionshare-gui.exe.manifest" - Delete "$INSTDIR\pyexpat.pyd" - Delete "$INSTDIR\PyQt5\Qt\bin\qt.conf" - Delete "$INSTDIR\PyQt5\Qt\plugins\iconengines\qsvgicon.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qgif.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qicns.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qico.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qjpeg.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qsvg.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qtga.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qtiff.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qwbmp.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\imageformats\qwebp.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qminimal.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qoffscreen.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\platforms\qwindows.dll" - Delete "$INSTDIR\PyQt5\Qt\plugins\printsupport\windowsprintersupport.dll" - Delete "$INSTDIR\PyQt5.Qt.pyd" - Delete "$INSTDIR\PyQt5.QtCore.pyd" - Delete "$INSTDIR\PyQt5.QtGui.pyd" - Delete "$INSTDIR\PyQt5.QtPrintSupport.pyd" - Delete "$INSTDIR\PyQt5.QtWidgets.pyd" - Delete "$INSTDIR\python3.dll" - Delete "$INSTDIR\python36.dll" - Delete "$INSTDIR\pythoncom36.dll" - Delete "$INSTDIR\pywintypes36.dll" - Delete "$INSTDIR\Qt5Core.dll" - Delete "$INSTDIR\Qt5Gui.dll" - Delete "$INSTDIR\Qt5PrintSupport.dll" - Delete "$INSTDIR\Qt5Svg.dll" - Delete "$INSTDIR\Qt5Widgets.dll" - Delete "$INSTDIR\select.pyd" - Delete "$INSTDIR\share\html\404.html" - Delete "$INSTDIR\share\html\denied.html" - Delete "$INSTDIR\share\html\index.html" - Delete "$INSTDIR\share\images\download_completed.png" - Delete "$INSTDIR\share\images\download_completed_none.png" - Delete "$INSTDIR\share\images\download_in_progress.png" - Delete "$INSTDIR\share\images\download_in_progress_none.png" - Delete "$INSTDIR\share\images\download_window_gray.png" - Delete "$INSTDIR\share\images\download_window_green.png" - Delete "$INSTDIR\share\images\favicon.ico" - Delete "$INSTDIR\share\images\file_delete.png" - Delete "$INSTDIR\share\images\info.png" - Delete "$INSTDIR\share\images\logo.png" - Delete "$INSTDIR\share\images\logo_transparent.png" - Delete "$INSTDIR\share\images\logo_grayscale.png" - Delete "$INSTDIR\share\images\server_started.png" - Delete "$INSTDIR\share\images\server_stopped.png" - Delete "$INSTDIR\share\images\server_working.png" - Delete "$INSTDIR\share\images\settings.png" - Delete "$INSTDIR\share\images\web_file.png" - Delete "$INSTDIR\share\images\web_folder.png" - Delete "$INSTDIR\share\locale\cs.json" - Delete "$INSTDIR\share\locale\de.json" - Delete "$INSTDIR\share\locale\en.json" - Delete "$INSTDIR\share\locale\eo.json" - Delete "$INSTDIR\share\locale\es.json" - Delete "$INSTDIR\share\locale\fi.json" - Delete "$INSTDIR\share\locale\fr.json" - Delete "$INSTDIR\share\locale\it.json" - Delete "$INSTDIR\share\locale\nl.json" - Delete "$INSTDIR\share\locale\no.json" - Delete "$INSTDIR\share\locale\pt.json" - Delete "$INSTDIR\share\locale\ru.json" - Delete "$INSTDIR\share\locale\tr.json" - Delete "$INSTDIR\share\torrc_template" - Delete "$INSTDIR\share\torrc_template-windows" - Delete "$INSTDIR\share\torrc_template-obfs4" - Delete "$INSTDIR\share\torrc_template-meek_lite_azure" - Delete "$INSTDIR\share\version.txt" - Delete "$INSTDIR\share\wordlist.txt" - Delete "$INSTDIR\sip.pyd" - Delete "$INSTDIR\tor\Data\Tor\geoip" - Delete "$INSTDIR\tor\Data\Tor\geoip6" - Delete "$INSTDIR\tor\Tor\libeay32.dll" - Delete "$INSTDIR\tor\Tor\libevent-2-0-5.dll" - Delete "$INSTDIR\tor\Tor\libevent_core-2-0-5.dll" - Delete "$INSTDIR\tor\Tor\libevent_extra-2-0-5.dll" - Delete "$INSTDIR\tor\Tor\libgcc_s_sjlj-1.dll" - Delete "$INSTDIR\tor\Tor\libssp-0.dll" - Delete "$INSTDIR\tor\Tor\obfs4proxy.exe" - Delete "$INSTDIR\tor\Tor\ssleay32.dll" - Delete "$INSTDIR\tor\Tor\tor.exe" - Delete "$INSTDIR\tor\Tor\zlib1.dll" - Delete "$INSTDIR\ucrtbase.dll" - Delete "$INSTDIR\unicodedata.pyd" - Delete "$INSTDIR\VCRUNTIME140.dll" - Delete "$INSTDIR\win32api.pyd" - Delete "$INSTDIR\win32com.shell.shell.pyd" - Delete "$INSTDIR\win32trace.pyd" - Delete "$INSTDIR\win32ui.pyd" - Delete "$INSTDIR\win32wnet.pyd" - Delete "$INSTDIR\_asyncio.pyd" - Delete "$INSTDIR\_bz2.pyd" - Delete "$INSTDIR\_ctypes.pyd" - Delete "$INSTDIR\_decimal.pyd" - Delete "$INSTDIR\_hashlib.pyd" - Delete "$INSTDIR\_lzma.pyd" - Delete "$INSTDIR\_multiprocessing.pyd" - Delete "$INSTDIR\_overlapped.pyd" - Delete "$INSTDIR\_socket.pyd" - Delete "$INSTDIR\_ssl.pyd" - Delete "$INSTDIR\_win32sysloader.pyd" - - Delete "$INSTDIR\onionshare.ico" - Delete "$INSTDIR\uninstall.exe" - - rmDir "$INSTDIR\Include" - rmDir "$INSTDIR\lib2to3\tests\data" - rmDir "$INSTDIR\lib2to3\tests" - rmDir "$INSTDIR\lib2to3" - rmDir "$INSTDIR\licenses" - rmDir "$INSTDIR\PyQt5\Qt\bin" - rmDir "$INSTDIR\PyQt5\Qt\plugins\iconengines" - rmDir "$INSTDIR\PyQt5\Qt\plugins\imageformats" - rmDir "$INSTDIR\PyQt5\Qt\plugins\platforms" - rmDir "$INSTDIR\PyQt5\Qt\plugins\printsupport" - rmDir "$INSTDIR\PyQt5\Qt\plugins" - rmDir "$INSTDIR\PyQt5\Qt" - rmDir "$INSTDIR\PyQt5" - rmDir "$INSTDIR\share\html" - rmDir "$INSTDIR\share\images" - rmDir "$INSTDIR\share\locale" - rmDir "$INSTDIR\share" - rmDir "$INSTDIR\tor\Data\Tor" - rmDir "$INSTDIR\tor\Data" - rmDir "$INSTDIR\tor\Tor" - rmDir "$INSTDIR\tor" - rmDir "$INSTDIR" + RMDir /r $INSTDIR # remove uninstaller information from the registry DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" -- cgit v1.2.3-54-g00ecf From b4a5280cf2648bda0f1a39e8b8fa4a4c47f79bb4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 16:19:07 -0700 Subject: While I'm at it, bump the version to 2.0.dev --- share/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/version.txt b/share/version.txt index 3a3cd8cc..22351bb8 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -1.3.1 +2.0.dev -- cgit v1.2.3-54-g00ecf From 0234ff5f37eefb333e50fec102462a2e3d699dbc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 16:28:54 -0700 Subject: Set self.cancel_compression to false in the set_file_info() function instead of Web's constructor, so it gets reset every time --- onionshare/web.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onionshare/web.py b/onionshare/web.py index dc0effdb..38ad398e 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -105,7 +105,6 @@ class Web(object): self.zip_filename = None self.zip_filesize = None self.zip_writer = None - self.cancel_compression = False self.security_headers = [ ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), @@ -518,6 +517,8 @@ class Web(object): page will need to display. This includes zipping up the file in order to get the zip file's name and size. """ + self.cancel_compression = False + # build file info list self.file_info = {'files': [], 'dirs': []} for filename in filenames: -- cgit v1.2.3-54-g00ecf From 5448c4f3453260940d720ebdb8aa061fbbc79aa6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 16:58:16 -0700 Subject: Remove some debug logs --- onionshare/onion.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index a10dc53c..9aef3191 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -457,7 +457,6 @@ class Onion(object): # key_type = "ED25519-V3" else: raise TorErrorProtocolError(strings._('error_invalid_private_key')) - self.common.log('Onion', 'Starting a hidden service with a saved private key') else: # Work out if we can support v3 onion services, which are preferred if Version(self.tor_version) >= Version('0.3.2.9') and not self.settings.get('use_legacy_v2_onions'): @@ -467,7 +466,6 @@ class Onion(object): # fall back to v2 onion services key_type = "RSA1024" key_content = onionkey.generate_v2_private_key()[0] - self.common.log('Onion', 'Starting a hidden service with a new private key') # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 -- cgit v1.2.3-54-g00ecf From cc265491fd92763a36cc332a41a2f6e011954b96 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 17:17:25 -0700 Subject: Properly handle exceptions in CLI, and pass the actual exception message in TorErrorProtocolError exceptions --- onionshare/__init__.py | 4 ++++ onionshare/onion.py | 31 ++++++++++++++++--------------- onionshare/onionshare.py | 1 + share/locale/en.json | 3 ++- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index becca93f..51210b6b 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -111,6 +111,10 @@ def main(cwd=None): except KeyboardInterrupt: print("") sys.exit() + except (TorTooOld, TorErrorProtocolError) as e: + print("") + print(e.args[0]) + sys.exit() # Prepare files to share print(strings._("preparing_files")) diff --git a/onionshare/onion.py b/onionshare/onion.py index 9aef3191..d213cd6a 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -443,18 +443,18 @@ class Onion(object): # is the key a v2 key? if onionkey.is_v2_key(key_content): key_type = "RSA1024" - # The below section is commented out because re-publishing - # a pre-prepared v3 private key is currently unstable in Tor. - # This is fixed upstream but won't reach stable until 0.3.5 - # (expected in December 2018) - # See https://trac.torproject.org/projects/tor/ticket/25552 - # Until then, we will deliberately not work with 'persistent' - # v3 onions, which should not be possible via the GUI settings - # anyway. - # Our ticket: https://github.com/micahflee/onionshare/issues/677 - # - # Assume it was a v3 key - # key_type = "ED25519-V3" + # The below section is commented out because re-publishing + # a pre-prepared v3 private key is currently unstable in Tor. + # This is fixed upstream but won't reach stable until 0.3.5 + # (expected in December 2018) + # See https://trac.torproject.org/projects/tor/ticket/25552 + # Until then, we will deliberately not work with 'persistent' + # v3 onions, which should not be possible via the GUI settings + # anyway. + # Our ticket: https://github.com/micahflee/onionshare/issues/677 + # + # Assume it was a v3 key + # key_type = "ED25519-V3" else: raise TorErrorProtocolError(strings._('error_invalid_private_key')) else: @@ -473,6 +473,7 @@ class Onion(object): basic_auth = None self.stealth = False + self.common.log('Onion', 'start_onion_service', 'key_type={}'.format(key_type)) try: if basic_auth != None: res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type=key_type, key_content=key_content) @@ -480,8 +481,8 @@ class Onion(object): # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type=key_type, key_content=key_content) - except ProtocolError: - raise TorErrorProtocolError(strings._('error_tor_protocol_error')) + except ProtocolError as e: + raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0])) self.service_id = res.service_id onion_host = self.service_id + '.onion' @@ -512,7 +513,7 @@ class Onion(object): self.settings.save() return onion_host else: - raise TorErrorProtocolError(strings._('error_tor_protocol_error')) + raise TorErrorProtocolError(strings._('error_tor_protocol_error_unknown')) def cleanup(self, stop_tor=True): """ diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index b710fa3c..32e56ba0 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -21,6 +21,7 @@ along with this program. If not, see . import os, shutil from . import common, strings +from .onion import TorTooOld, TorErrorProtocolError from .common import ShutdownTimer class OnionShare(object): diff --git a/share/locale/en.json b/share/locale/en.json index 4e7143d3..7d3daba8 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -122,7 +122,8 @@ "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", - "error_tor_protocol_error": "Could not communicate with the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "error_tor_protocol_error": "There was an error with Tor: {}", + "error_tor_protocol_error_unknown": "There was an unknown error with Tor", "error_invalid_private_key": "This private key type is unsupported", "connecting_to_tor": "Connecting to the Tor network", "update_available": "A new version of OnionShare is available. Click here to download it.

    Installed version: {}
    Latest version: {}", -- cgit v1.2.3-54-g00ecf From 6da5ae84f7a719e71ec25464b9f4b47edbb714c0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 17:31:40 -0700 Subject: Tor version must actually be 0.3.3.1 for ephemeral v3 onion services --- onionshare/onion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index d213cd6a..7a111eff 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -459,7 +459,7 @@ class Onion(object): raise TorErrorProtocolError(strings._('error_invalid_private_key')) else: # Work out if we can support v3 onion services, which are preferred - if Version(self.tor_version) >= Version('0.3.2.9') and not self.settings.get('use_legacy_v2_onions'): + if Version(self.tor_version) >= Version('0.3.3.1') and not self.settings.get('use_legacy_v2_onions'): key_type = "ED25519-V3" key_content = onionkey.generate_v3_private_key()[0] else: -- cgit v1.2.3-54-g00ecf From d63808f419df38cad9e9d9bd96f80778945f24c2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Sep 2018 17:44:54 -0700 Subject: Import onion exceptions that were missing --- onionshare_gui/threads.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 3c99d395..f4acc5e1 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -20,6 +20,8 @@ along with this program. If not, see . import time from PyQt5 import QtCore +from onionshare.onion import * + class OnionThread(QtCore.QThread): """ -- cgit v1.2.3-54-g00ecf From c08f6f3db1b9d0ea545a83949b55958724537861 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 10:57:12 +1000 Subject: #704 wrap the upload filename label if too long --- onionshare_gui/receive_mode/uploads.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 1aebe902..e04e0269 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -19,6 +19,7 @@ along with this program. If not, see . """ import os import subprocess +import textwrap from datetime import datetime from PyQt5 import QtCore, QtWidgets, QtGui @@ -305,10 +306,7 @@ class Uploads(QtWidgets.QScrollArea): try: for upload in self.uploads.values(): for item in upload.files.values(): - if item.filename_label_width > width: - item.filename_label.setText(item.filename[:25] + '[...]') - item.adjustSize() - if width > item.filename_label_width: - item.filename_label.setText(item.filename) + item.filename_label.setText(textwrap.fill(item.filename, 30)) + item.adjustSize() except: pass -- cgit v1.2.3-54-g00ecf From 41b4cea673d5b1ea43e2d7eda6caecd9a77bde3c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 11:24:19 +1000 Subject: Open folder containing uploads with the file selected, in macOS and Windows --- onionshare_gui/receive_mode/uploads.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 1aebe902..4a383421 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -92,15 +92,11 @@ class File(QtWidgets.QWidget): # 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') + subprocess.call(['open', '-R', abs_filename]) # 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') + webbrowser.Popen(['explorer', '/select', abs_filename]) class Upload(QtWidgets.QWidget): -- cgit v1.2.3-54-g00ecf From 388f96855663d4f69bf0ed88b8feab68cd7e5877 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 11:27:36 +1000 Subject: fix function name for windows --- onionshare_gui/receive_mode/uploads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 4a383421..bf502f99 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -96,7 +96,7 @@ class File(QtWidgets.QWidget): # Windows elif self.common.platform == 'Windows': - webbrowser.Popen(['explorer', '/select', abs_filename]) + subprocess.Popen(['explorer', '/select', abs_filename]) class Upload(QtWidgets.QWidget): -- cgit v1.2.3-54-g00ecf From f45eae5768f75d2843f835a135708f1b521f0068 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 12:12:18 +1000 Subject: Fix syntax for opening explorer and selecting the filename. Fix a bug in the downloads_dir for Windows (need to use a backslash) --- onionshare/settings.py | 10 +++++++--- onionshare_gui/receive_mode/uploads.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index adcfc7a3..073798e5 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -105,9 +105,13 @@ class Settings(object): """ Returns the path of the default Downloads directory for receive mode. """ - # TODO: Test in Windows, though it looks like it should work - # https://docs.python.org/3/library/os.path.html#os.path.expanduser - return os.path.expanduser('~/OnionShare') + # On Windows, os.path.expanduser() needs to use backslash, or else it + # retains the forward slash, which breaks opening the folder in explorer. + p = platform.system() + if p == 'Windows': + return os.path.expanduser('~\OnionShare') + else: + return os.path.expanduser('~/OnionShare') def load(self): """ diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index bf502f99..8d95d984 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -96,7 +96,7 @@ class File(QtWidgets.QWidget): # Windows elif self.common.platform == 'Windows': - subprocess.Popen(['explorer', '/select', abs_filename]) + subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) class Upload(QtWidgets.QWidget): -- cgit v1.2.3-54-g00ecf From 28674bf023b15f34900a53ebda72352f2be58cec Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 14:37:07 +1000 Subject: Add 'clear history' button to downloads and uploads windows --- onionshare_gui/receive_mode/uploads.py | 8 ++++++++ onionshare_gui/share_mode/downloads.py | 7 +++++++ share/locale/en.json | 1 + 3 files changed, 16 insertions(+) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index d52626c5..1c9afdd2 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -232,6 +232,10 @@ class Uploads(QtWidgets.QScrollArea): uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True)) + self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + self.clear_history_button.clicked.connect(self.reset) + self.clear_history_button.hide() + self.uploads_layout = QtWidgets.QVBoxLayout() @@ -239,6 +243,7 @@ class Uploads(QtWidgets.QScrollArea): 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) @@ -257,6 +262,8 @@ class Uploads(QtWidgets.QScrollArea): 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) @@ -299,6 +306,7 @@ class Uploads(QtWidgets.QScrollArea): self.uploads = {} self.no_uploads_label.show() + self.clear_history_button.hide() self.resize(self.sizeHint()) def resizeEvent(self, event): diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 538ddfd0..9aeef576 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -102,6 +102,9 @@ class Downloads(QtWidgets.QScrollArea): downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + self.clear_history_button.clicked.connect(self.reset) + self.clear_history_button.hide() self.downloads_layout = QtWidgets.QVBoxLayout() @@ -109,6 +112,7 @@ class Downloads(QtWidgets.QScrollArea): 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) @@ -126,6 +130,8 @@ class Downloads(QtWidgets.QScrollArea): """ # 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) @@ -154,4 +160,5 @@ class Downloads(QtWidgets.QScrollArea): self.downloads = {} self.no_downloads_label.show() + self.clear_history_button.hide() self.resize(self.sizeHint()) diff --git a/share/locale/en.json b/share/locale/en.json index 7d3daba8..250524be 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -181,6 +181,7 @@ "gui_uploads": "Upload History", "gui_uploads_window_tooltip": "Show/hide uploads", "gui_no_uploads": "No uploads yet.", + "gui_clear_history": "Clear history", "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", -- cgit v1.2.3-54-g00ecf From 20c97a61ab74ebbf33bb20a27082b0068539b7c8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 14:40:30 +1000 Subject: Send the public_mode argument to web.start in the WebThread --- onionshare_gui/threads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index f4acc5e1..9129600d 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -73,4 +73,4 @@ class WebThread(QtCore.QThread): def run(self): self.mode.common.log('WebThread', 'run') - self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('slug')) + self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.common.settings.get('slug')) -- cgit v1.2.3-54-g00ecf From abb270834b127feb0e283b357fbbce3afe3fed23 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 14:53:33 +1000 Subject: Log the public_mode flag as well --- onionshare/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/web.py b/onionshare/web.py index 38ad398e..067c5e07 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -619,7 +619,7 @@ class Web(object): """ Start the flask web server. """ - self.common.log('Web', 'start', 'port={}, stay_open={}, persistent_slug={}'.format(port, stay_open, persistent_slug)) + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) if not public_mode: self.generate_slug(persistent_slug) -- cgit v1.2.3-54-g00ecf From 8b704a007050048a0e51d90e89af3e15e7b218b4 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 19 Sep 2018 15:07:04 +1000 Subject: Start the WebThread earlier than the Onion service. This gives it a chance to generate its slug before the Onion Service finishes starting up, which can otherwise lead to a crash --- onionshare_gui/threads.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index f4acc5e1..2af0120d 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -41,14 +41,6 @@ class OnionThread(QtCore.QThread): def run(self): self.mode.common.log('OnionThread', 'run') - try: - self.mode.app.start_onion_service() - self.success.emit() - - except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: - self.error.emit(e.args[0]) - return - self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download') # start onionshare http service in new thread @@ -58,6 +50,14 @@ class OnionThread(QtCore.QThread): # wait for modules in thread to load, preventing a thread-related cx_Freeze crash time.sleep(0.2) + try: + self.mode.app.start_onion_service() + self.success.emit() + + except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e: + self.error.emit(e.args[0]) + return + class WebThread(QtCore.QThread): """ -- cgit v1.2.3-54-g00ecf From fc7afecb7bd1e4da0e8b419dd58097df61e6453c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 18:14:42 -0700 Subject: Choose a port *before* starting the web service --- onionshare_gui/threads.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py index 2af0120d..83114aea 100644 --- a/onionshare_gui/threads.py +++ b/onionshare_gui/threads.py @@ -73,4 +73,5 @@ class WebThread(QtCore.QThread): def run(self): self.mode.common.log('WebThread', 'run') + self.mode.app.choose_port() self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('slug')) -- cgit v1.2.3-54-g00ecf From 997e2f87ee003616388b96e65471500490a773ba Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 20 Sep 2018 11:33:37 +1000 Subject: Throw a 403 on the index pages if the timer has run out but an upload is in progress --- onionshare/web.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/onionshare/web.py b/onionshare/web.py index 1c285ea7..f52d9dd6 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -340,10 +340,14 @@ class Web(object): @self.app.route("/") def index(slug_candidate): self.check_slug_candidate(slug_candidate) + if not self.can_upload: + return self.error403() return index_logic() @self.app.route("/") def index_public(): + if not self.can_upload: + return self.error403() if not self.common.settings.get('public_mode'): return self.error404() return index_logic() -- cgit v1.2.3-54-g00ecf From 59003635a2c736cda1323e48935a25532ffeda56 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 18:50:42 -0700 Subject: Add a horizontal layout wrapper around the vertical layout, in order to optionally add horizontal widgets to Modes --- onionshare_gui/mode.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index feb2f5b6..7ae8d315 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -78,7 +78,11 @@ class Mode(QtWidgets.QWidget): # Layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.primary_action) - self.setLayout(self.layout) + + self.horizontal_layout_wrapper = QtWidgets.QHBoxLayout() + self.horizontal_layout_wrapper.addLayout(self.layout) + + self.setLayout(self.horizontal_layout_wrapper) def init(self): """ -- cgit v1.2.3-54-g00ecf From 499f7b16381fb2f1a8ec3bb50d7128f9c0062d9a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 19:00:12 -0700 Subject: Always show uploads and downloads --- onionshare_gui/mode.py | 4 ++++ onionshare_gui/receive_mode/__init__.py | 3 ++- onionshare_gui/receive_mode/uploads.py | 1 - onionshare_gui/share_mode/__init__.py | 1 + onionshare_gui/share_mode/downloads.py | 1 - 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 7ae8d315..4c21de76 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -78,6 +78,10 @@ class Mode(QtWidgets.QWidget): # Layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.primary_action) + # Hack to allow a minimum width on self.layout + min_width_widget = QtWidgets.QWidget() + min_width_widget.setMinimumWidth(450) + self.layout.addWidget(min_width_widget) self.horizontal_layout_wrapper = QtWidgets.QHBoxLayout() self.horizontal_layout_wrapper.addLayout(self.layout) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index d414f3b0..0f3105e1 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -46,7 +46,7 @@ class ReceiveMode(Mode): self.server_status.web = self.web self.server_status.update() - # Downloads + # Uploads self.uploads = Uploads(self.common) self.uploads_in_progress = 0 self.uploads_completed = 0 @@ -86,6 +86,7 @@ class ReceiveMode(Mode): # Layout self.layout.insertWidget(0, self.receive_info) self.layout.insertWidget(0, self.info_widget) + self.horizontal_layout_wrapper.addWidget(self.uploads) def get_stop_server_shutdown_timeout_text(self): """ diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index f77cdbf4..48574cc7 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -222,7 +222,6 @@ class Uploads(QtWidgets.QScrollArea): self.setWindowTitle(strings._('gui_uploads', True)) self.setWidgetResizable(True) - self.setMaximumHeight(600) self.setMinimumHeight(150) self.setMinimumWidth(350) self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 65ce1d52..28881220 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -116,6 +116,7 @@ class ShareMode(Mode): # Layout self.layout.insertLayout(0, self.file_selection) self.layout.insertWidget(0, self.info_widget) + self.horizontal_layout_wrapper.addWidget(self.downloads) # Always start with focus on file selection self.file_selection.setFocus() diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 9aeef576..a34796f1 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -91,7 +91,6 @@ class Downloads(QtWidgets.QScrollArea): self.setWindowTitle(strings._('gui_downloads', True)) self.setWidgetResizable(True) - self.setMaximumHeight(600) self.setMinimumHeight(150) self.setMinimumWidth(350) self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) -- cgit v1.2.3-54-g00ecf From 1314ddf1bec3a87c1bdbdb27412677194102aeea Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 19:36:16 -0700 Subject: Remove upload/download toggle buttons --- onionshare_gui/mode.py | 1 + onionshare_gui/receive_mode/__init__.py | 19 ------------------- onionshare_gui/share_mode/__init__.py | 19 ------------------- share/images/download_window_gray.png | Bin 440 -> 0 bytes share/images/download_window_green.png | Bin 761 -> 0 bytes share/images/upload_window_gray.png | Bin 298 -> 0 bytes share/images/upload_window_green.png | Bin 483 -> 0 bytes 7 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 share/images/download_window_gray.png delete mode 100644 share/images/download_window_green.png delete mode 100644 share/images/upload_window_gray.png delete mode 100644 share/images/upload_window_green.png diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 4c21de76..dad58bf3 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -78,6 +78,7 @@ class Mode(QtWidgets.QWidget): # Layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.primary_action) + self.layout.addStretch() # Hack to allow a minimum width on self.layout min_width_widget = QtWidgets.QWidget() min_width_widget.setMinimumWidth(450) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 0f3105e1..74b67e32 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -53,12 +53,6 @@ class ReceiveMode(Mode): self.new_upload = False # For scrolling to the bottom of the uploads list # Information about share, and show uploads button - self.info_show_uploads = QtWidgets.QToolButton() - self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png'))) - self.info_show_uploads.setCheckable(True) - self.info_show_uploads.toggled.connect(self.uploads_toggled) - self.info_show_uploads.setToolTip(strings._('gui_uploads_window_tooltip', True)) - self.info_in_progress_uploads_count = QtWidgets.QLabel() self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) @@ -72,7 +66,6 @@ class ReceiveMode(Mode): self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_uploads_count) self.info_layout.addWidget(self.info_completed_uploads_count) - self.info_layout.addWidget(self.info_show_uploads) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -189,7 +182,6 @@ class ReceiveMode(Mode): self.uploads_in_progress = 0 self.update_uploads_completed() self.update_uploads_in_progress() - self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png'))) self.uploads.reset() def update_uploads_completed(self): @@ -211,7 +203,6 @@ class ReceiveMode(Mode): image = self.common.get_resource_path('images/share_in_progress_none.png') else: image = self.common.get_resource_path('images/share_in_progress.png') - self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png'))) self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) @@ -226,13 +217,3 @@ class ReceiveMode(Mode): # Resize window self.adjustSize() - - def uploads_toggled(self, checked): - """ - When the 'Show/hide uploads' button is toggled, show or hide the uploads window. - """ - self.common.log('ReceiveMode', 'toggle_uploads') - if checked: - self.uploads.show() - else: - self.uploads.hide() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 28881220..a9c6e8d7 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -79,12 +79,6 @@ class ShareMode(Mode): self.info_label = QtWidgets.QLabel() self.info_label.setStyleSheet(self.common.css['mode_info_label']) - self.info_show_downloads = QtWidgets.QToolButton() - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) - self.info_show_downloads.setCheckable(True) - self.info_show_downloads.toggled.connect(self.downloads_toggled) - self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True)) - self.info_in_progress_downloads_count = QtWidgets.QLabel() self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) @@ -99,7 +93,6 @@ class ShareMode(Mode): self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) - self.info_layout.addWidget(self.info_show_downloads) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -316,16 +309,6 @@ class ShareMode(Mode): # Resize window self.adjustSize() - def downloads_toggled(self, checked): - """ - When the 'Show/hide downloads' button is toggled, show or hide the downloads window. - """ - self.common.log('ShareMode', 'toggle_downloads') - if checked: - self.downloads.show() - else: - self.downloads.hide() - def reset_info_counters(self): """ Set the info counters back to zero. @@ -334,7 +317,6 @@ class ShareMode(Mode): self.downloads_in_progress = 0 self.update_downloads_completed() self.update_downloads_in_progress() - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png'))) self.downloads.reset() def update_downloads_completed(self): @@ -356,7 +338,6 @@ class ShareMode(Mode): image = self.common.get_resource_path('images/share_in_progress_none.png') else: image = self.common.get_resource_path('images/share_in_progress.png') - self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png'))) self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) diff --git a/share/images/download_window_gray.png b/share/images/download_window_gray.png deleted file mode 100644 index bf9c168e..00000000 Binary files a/share/images/download_window_gray.png and /dev/null differ diff --git a/share/images/download_window_green.png b/share/images/download_window_green.png deleted file mode 100644 index 8f9a899b..00000000 Binary files a/share/images/download_window_green.png and /dev/null differ diff --git a/share/images/upload_window_gray.png b/share/images/upload_window_gray.png deleted file mode 100644 index 80db4b8f..00000000 Binary files a/share/images/upload_window_gray.png and /dev/null differ diff --git a/share/images/upload_window_green.png b/share/images/upload_window_green.png deleted file mode 100644 index 652ddaff..00000000 Binary files a/share/images/upload_window_green.png and /dev/null differ -- cgit v1.2.3-54-g00ecf From 871135cc750b1d5ec3cbf7818eff96ccc3989652 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 19:38:29 -0700 Subject: Only add a stretch at the bottom of the layout in receive mode, not share mode --- onionshare_gui/mode.py | 1 - onionshare_gui/receive_mode/__init__.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index dad58bf3..4c21de76 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -78,7 +78,6 @@ class Mode(QtWidgets.QWidget): # Layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.primary_action) - self.layout.addStretch() # Hack to allow a minimum width on self.layout min_width_widget = QtWidgets.QWidget() min_width_widget.setMinimumWidth(450) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 74b67e32..8712653b 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -79,6 +79,7 @@ class ReceiveMode(Mode): # Layout self.layout.insertWidget(0, self.receive_info) self.layout.insertWidget(0, self.info_widget) + self.layout.addStretch() self.horizontal_layout_wrapper.addWidget(self.uploads) def get_stop_server_shutdown_timeout_text(self): -- cgit v1.2.3-54-g00ecf From beeebce6312cee691ef7e2320459396d0312e0e6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 19:39:59 -0700 Subject: Set minimum width for whole application --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index b63119bb..07c82e50 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -55,7 +55,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.setMinimumWidth(450) + self.setMinimumWidth(850) # Load settings self.config = config -- cgit v1.2.3-54-g00ecf From 5a96bcc77b7fced58491b1ad908ea610e63a8f11 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 20:17:23 -0700 Subject: Remove adjustSize that was causing issues --- onionshare_gui/onionshare_gui.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 07c82e50..8b61a18e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -205,9 +205,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.update_server_status_indicator() - # Wait 1ms for the event loop to finish, then adjust size - QtCore.QTimer.singleShot(1, self.adjustSize) - def share_mode_clicked(self): if self.mode != self.MODE_SHARE: self.common.log('OnionShareGui', 'share_mode_clicked') -- cgit v1.2.3-54-g00ecf From dbae5016895f1e29a15188aed4f21d0afb3350b4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Sep 2018 21:44:37 -0700 Subject: Remove obsolete strings (#770) --- share/locale/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index 250524be..e6b2b2c0 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -48,7 +48,6 @@ "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", "gui_downloads": "Download History", - "gui_downloads_window_tooltip": "Show/hide downloads", "gui_no_downloads": "No downloads yet.", "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare address", @@ -179,7 +178,6 @@ "systray_download_page_loaded_message": "A user loaded the download page", "systray_upload_page_loaded_message": "A user loaded the upload page", "gui_uploads": "Upload History", - "gui_uploads_window_tooltip": "Show/hide uploads", "gui_no_uploads": "No uploads yet.", "gui_clear_history": "Clear history", "gui_upload_in_progress": "Upload Started {}", -- cgit v1.2.3-54-g00ecf From 324538bdd36382919d7f01c99b16b9b82b56603f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 09:14:56 -0700 Subject: When there is only 1 file being shared, don't zip it --- onionshare/__init__.py | 4 +- onionshare/web.py | 72 ++++++++++++++++++++--------------- onionshare_gui/share_mode/threads.py | 2 +- screenshots/server.png | Bin 45923 -> 0 bytes share/templates/send.html | 24 ++++++------ 5 files changed, 57 insertions(+), 45 deletions(-) delete mode 100644 screenshots/server.png diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 51210b6b..e04836b7 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -120,13 +120,13 @@ def main(cwd=None): print(strings._("preparing_files")) try: web.set_file_info(filenames) - app.cleanup_filenames.append(web.zip_filename) + app.cleanup_filenames.append(web.download_filename) except OSError as e: print(e.strerror) sys.exit(1) # Warn about sending large files over Tor - if web.zip_filesize >= 157286400: # 150mb + if web.download_filesize >= 157286400: # 150mb print('') print(strings._("large_filesize")) print('') diff --git a/onionshare/web.py b/onionshare/web.py index 067c5e07..2575230f 100644 --- a/onionshare/web.py +++ b/onionshare/web.py @@ -102,8 +102,9 @@ class Web(object): # Information about the file self.file_info = [] - self.zip_filename = None - self.zip_filesize = None + self.is_zipped = False + self.download_filename = None + self.download_filesize = None self.zip_writer = None self.security_headers = [ @@ -182,17 +183,19 @@ class Web(object): 'send.html', slug=self.slug, file_info=self.file_info, - filename=os.path.basename(self.zip_filename), - filesize=self.zip_filesize, - filesize_human=self.common.human_readable_filesize(self.zip_filesize))) + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) else: # If download is allowed to continue, serve download page r = make_response(render_template( 'send.html', file_info=self.file_info, - filename=os.path.basename(self.zip_filename), - filesize=self.zip_filesize, - filesize_human=self.common.human_readable_filesize(self.zip_filesize))) + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) return self.add_security_headers(r) @self.app.route("//download") @@ -231,8 +234,8 @@ class Web(object): 'id': download_id} ) - dirname = os.path.dirname(self.zip_filename) - basename = os.path.basename(self.zip_filename) + dirname = os.path.dirname(self.download_filename) + basename = os.path.basename(self.download_filename) def generate(): # The user hasn't canceled the download @@ -244,7 +247,7 @@ class Web(object): chunk_size = 102400 # 100kb - fp = open(self.zip_filename, 'rb') + fp = open(self.download_filename, 'rb') self.done = False canceled = False while not self.done: @@ -264,7 +267,7 @@ class Web(object): # tell GUI the progress downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.zip_filesize) * 100 + percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -308,7 +311,7 @@ class Web(object): pass r = Response(generate()) - r.headers.set('Content-Length', self.zip_filesize) + r.headers.set('Content-Length', self.download_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) r = self.add_security_headers(r) # guess content type @@ -517,8 +520,9 @@ class Web(object): page will need to display. This includes zipping up the file in order to get the zip file's name and size. """ + self.common.log("Web", "set_file_info") self.cancel_compression = False - + # build file info list self.file_info = {'files': [], 'dirs': []} for filename in filenames: @@ -537,22 +541,30 @@ class Web(object): self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) - # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) - self.zip_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) - # Canceling early? - if self.cancel_compression: - self.zip_writer.close() - return False - - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): - return False - - self.zip_writer.close() - self.zip_filesize = os.path.getsize(self.zip_filename) + # Check if there's only 1 file and no folders + if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: + self.is_zipped = False + self.download_filename = self.file_info['files'][0]['filename'] + self.download_filesize = self.file_info['files'][0]['size'] + else: + # Zip up the files and folders + self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.download_filename = self.zip_writer.zip_filename + for info in self.file_info['files']: + self.zip_writer.add_file(info['filename']) + # Canceling early? + if self.cancel_compression: + self.zip_writer.close() + return False + + for info in self.file_info['dirs']: + if not self.zip_writer.add_dir(info['filename']): + return False + + self.zip_writer.close() + self.download_filesize = os.path.getsize(self.download_filename) + self.is_zipped = True + return True def _safe_select_jinja_autoescape(self, filename): diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index 50789049..9cda76b1 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -47,7 +47,7 @@ class CompressThread(QtCore.QThread): # Cancelled pass - self.mode.app.cleanup_filenames.append(self.mode.web.zip_filename) + self.mode.app.cleanup_filenames.append(self.mode.web.download_filename) except OSError as e: self.error.emit(e.strerror) diff --git a/screenshots/server.png b/screenshots/server.png deleted file mode 100644 index 8bdf2971..00000000 Binary files a/screenshots/server.png and /dev/null differ diff --git a/share/templates/send.html b/share/templates/send.html index df1d3563..e7e1fde0 100644 --- a/share/templates/send.html +++ b/share/templates/send.html @@ -10,18 +10,18 @@
    -
    - -
    - -

    OnionShare

    +
    +
      +
    • Total size: {{ filesize_human }} {% if is_zipped %} (compressed){% endif %}
    • + {% if slug %} +
    • Download Files
    • + {% else %} +
    • Download Files
    • + {% endif %} +
    +
    + +

    OnionShare

    -- cgit v1.2.3-54-g00ecf From 696665815e5c93eeff196bfd46acbeb6f978dfc5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 09:26:11 -0700 Subject: Make fedora and debian dependencies get added to packages built --- BUILD.md | 4 ++-- install/build_rpm.sh | 2 +- stdeb.cfg | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 51f5cadd..1feedf49 100644 --- a/BUILD.md +++ b/BUILD.md @@ -11,11 +11,11 @@ cd onionshare Install the needed dependencies: -For Debian-like distros: `apt install -y build-essential fakeroot python3-all python3-stdeb dh-python python3-flask python3-stem python3-pyqt5 python-nautilus python3-pytest tor obfs4proxy python3-cryptography python3-crypto python3-nacl python3-pip python3-socks python3-sha3` +For Debian-like distros: `apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python` On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available. -For Fedora-like distros: `dnf install -y rpm-build python3-flask python3-stem python3-qt5 python3-pytest nautilus-python tor obfs4 python3-pynacl python3-cryptography python3-crypto python3-pip python3-pysocks` +For Fedora-like distros: `dnf install -y python3-flask python3-stem python3-qt5 python3-pynacl python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/install/build_rpm.sh b/install/build_rpm.sh index c103262c..3f7a68ac 100755 --- a/install/build_rpm.sh +++ b/install/build_rpm.sh @@ -9,7 +9,7 @@ VERSION=`cat share/version.txt` rm -r build dist >/dev/null 2>&1 # build binary package -python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, nautilus-python, tor, obfs4" +python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-pynacl, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4" # install it echo "" diff --git a/stdeb.cfg b/stdeb.cfg index e190fe8b..2fc3d3bf 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3-flask, python3-stem, python3-pyqt5, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5 +Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy +Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy Suite: bionic X-Python3-Version: >= 3.6 -- cgit v1.2.3-54-g00ecf From 12a5b68d16084499d79f3338ce4dfb341773c1fa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 09:30:44 -0700 Subject: Also package the new python modules --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1a665085..a36fecab 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,12 @@ setup( description=description, long_description=long_description, author=author, author_email=author_email, url=url, license=license, keywords=keywords, - packages=['onionshare', 'onionshare_gui'], + packages=[ + 'onionshare', + 'onionshare_gui', + 'onionshare_gui.share_mode', + 'onionshare_gui.receive_mode' + ], include_package_data=True, scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'], data_files=data_files -- cgit v1.2.3-54-g00ecf From ea938e243985aa64848ec726fdf21d926b7cb222 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 12:25:07 -0700 Subject: Change more references to web.zip_filesize to be refer to web.download_filesize --- onionshare_gui/share_mode/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index a9c6e8d7..aec32305 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -177,7 +177,7 @@ class ShareMode(Mode): self._zip_progress_bar = None # Warn about sending large files over Tor - if self.web.zip_filesize >= 157286400: # 150mb + if self.web.download_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() @@ -229,7 +229,7 @@ class ShareMode(Mode): """ Handle REQUEST_STARTED event. """ - self.downloads.add(event["data"]["id"], self.web.zip_filesize) + self.downloads.add(event["data"]["id"], self.web.download_filesize) self.downloads_in_progress += 1 self.update_downloads_in_progress() @@ -242,7 +242,7 @@ class ShareMode(Mode): self.downloads.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? - if event["data"]["bytes"] == self.web.zip_filesize: + if event["data"]["bytes"] == self.web.download_filesize: self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info -- cgit v1.2.3-54-g00ecf From 5bc8e0a5e55bfaa5b6f4b06702d3537fb5c7a35a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:09:14 -0700 Subject: Smoothly quit when Ctrl-C is pressed --- onionshare_gui/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 13f0e8c7..99db635a 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -18,7 +18,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ from __future__ import division -import os, sys, platform, argparse +import os +import sys +import platform +import argparse +import signal from .widgets import Alert from PyQt5 import QtCore, QtWidgets @@ -58,6 +62,10 @@ def main(): strings.load_strings(common) print(strings._('version_string').format(common.version)) + # Allow Ctrl-C to smoothly quit the program instead of throwing an exception + # https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt + signal.signal(signal.SIGINT, signal.SIG_DFL) + # Start the Qt app global qtapp qtapp = Application(common) -- cgit v1.2.3-54-g00ecf From 81fa5e052cf4e959670f02f738c35c1148857b91 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:18:17 -0700 Subject: Only add the download_filename to cleanup_filenames (which get deleted) if the file is zipped up. Otherwise, OnionShare deletes the original file --- onionshare/__init__.py | 3 ++- onionshare_gui/share_mode/threads.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index e04836b7..2f57ccf2 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -120,7 +120,8 @@ def main(cwd=None): print(strings._("preparing_files")) try: web.set_file_info(filenames) - app.cleanup_filenames.append(web.download_filename) + if web.is_zipped: + app.cleanup_filenames.append(web.download_filename) except OSError as e: print(e.strerror) sys.exit(1) diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index 9cda76b1..dc43bf0a 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -47,7 +47,8 @@ class CompressThread(QtCore.QThread): # Cancelled pass - self.mode.app.cleanup_filenames.append(self.mode.web.download_filename) + if self.mode.web.is_zipped: + self.mode.app.cleanup_filenames.append(self.mode.web.download_filename) except OSError as e: self.error.emit(e.strerror) -- cgit v1.2.3-54-g00ecf From 603be8a02c9984c5b9f97394b3038916bc56456a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:31:52 -0700 Subject: Make web a module, so I can split it into multiple files --- onionshare/web.py | 876 --------------------------------------------- onionshare/web/__init__.py | 876 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 876 insertions(+), 876 deletions(-) delete mode 100644 onionshare/web.py create mode 100644 onionshare/web/__init__.py diff --git a/onionshare/web.py b/onionshare/web.py deleted file mode 100644 index 2575230f..00000000 --- a/onionshare/web.py +++ /dev/null @@ -1,876 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import hmac -import logging -import mimetypes -import os -import queue -import socket -import sys -import tempfile -import zipfile -import re -import io -from distutils.version import LooseVersion as Version -from urllib.request import urlopen -from datetime import datetime - -import flask -from flask import ( - Flask, Response, Request, request, render_template, abort, make_response, - flash, redirect, __version__ as flask_version -) -from werkzeug.utils import secure_filename - -from . import strings -from .common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable - - -# Stub out flask's show_server_banner function, to avoiding showing warnings that -# are not applicable to OnionShare -def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): - pass - -flask.cli.show_server_banner = stubbed_show_server_banner - - -class Web(object): - """ - The Web object is the OnionShare web server, powered by flask - """ - REQUEST_LOAD = 0 - REQUEST_STARTED = 1 - REQUEST_PROGRESS = 2 - REQUEST_OTHER = 3 - REQUEST_CANCELED = 4 - REQUEST_RATE_LIMIT = 5 - REQUEST_CLOSE_SERVER = 6 - REQUEST_UPLOAD_FILE_RENAMED = 7 - REQUEST_UPLOAD_FINISHED = 8 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 - REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 - - def __init__(self, common, gui_mode, receive_mode=False): - self.common = common - - # The flask app - self.app = Flask(__name__, - static_folder=self.common.get_resource_path('static'), - template_folder=self.common.get_resource_path('templates')) - self.app.secret_key = self.common.random_string(8) - - # Debug mode? - if self.common.debug: - self.debug_mode() - - # Are we running in GUI mode? - self.gui_mode = gui_mode - - # Are we using receive mode? - self.receive_mode = receive_mode - if self.receive_mode: - # Use custom WSGI middleware, to modify environ - self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) - # Use a custom Request class to track upload progess - self.app.request_class = ReceiveModeRequest - - # Starting in Flask 0.11, render_template_string autoescapes template variables - # by default. To prevent content injection through template variables in - # earlier versions of Flask, we force autoescaping in the Jinja2 template - # engine if we detect a Flask version with insecure default behavior. - if Version(flask_version) < Version('0.11'): - # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc - Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape - - # Information about the file - self.file_info = [] - self.is_zipped = False - self.download_filename = None - self.download_filesize = None - self.zip_writer = None - - self.security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') - ] - - self.q = queue.Queue() - - self.slug = None - - self.download_count = 0 - self.upload_count = 0 - - self.error404_count = 0 - - # If "Stop After First Download" is checked (stay_open == False), only allow - # one download at a time. - self.download_in_progress = False - - self.done = False - - # If the client closes the OnionShare window while a download is in progress, - # it should immediately stop serving the file. The client_cancel global is - # used to tell the download function that the client is canceling the download. - self.client_cancel = False - - # shutting down the server only works within the context of flask, so the easiest way to do it is over http - self.shutdown_slug = self.common.random_string(16) - - # Keep track if the server is running - self.running = False - - # Define the ewb app routes - self.common_routes() - if self.receive_mode: - self.receive_routes() - else: - self.send_routes() - - def send_routes(self): - """ - The web app routes for sharing files - """ - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - def index_logic(slug_candidate=''): - """ - Render the template for the onionshare landing page. - """ - self.add_request(Web.REQUEST_LOAD, request.path) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # If download is allowed to continue, serve download page - if self.slug: - r = make_response(render_template( - 'send.html', - slug=self.slug, - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - else: - # If download is allowed to continue, serve download page - r = make_response(render_template( - 'send.html', - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - return self.add_security_headers(r) - - @self.app.route("//download") - def download(slug_candidate): - self.check_slug_candidate(slug_candidate) - return download_logic() - - @self.app.route("/download") - def download_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return download_logic() - - def download_logic(slug_candidate=''): - """ - Download the zip file. - """ - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # Each download has a unique id - download_id = self.download_count - self.download_count += 1 - - # Prepare some variables to use inside generate() function below - # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') - path = request.path - - # Tell GUI the download started - self.add_request(Web.REQUEST_STARTED, path, { - 'id': download_id} - ) - - dirname = os.path.dirname(self.download_filename) - basename = os.path.basename(self.download_filename) - - def generate(): - # The user hasn't canceled the download - self.client_cancel = False - - # Starting a new download - if not self.stay_open: - self.download_in_progress = True - - chunk_size = 102400 # 100kb - - fp = open(self.download_filename, 'rb') - self.done = False - canceled = False - while not self.done: - # The user has canceled the download, so stop serving the file - if self.client_cancel: - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - break - - chunk = fp.read(chunk_size) - if chunk == b'': - self.done = True - else: - try: - yield chunk - - # tell GUI the progress - downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 - - # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': - sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) - sys.stdout.flush() - - self.add_request(Web.REQUEST_PROGRESS, path, { - 'id': download_id, - 'bytes': downloaded_bytes - }) - self.done = False - except: - # looks like the download was canceled - self.done = True - canceled = True - - # tell the GUI the download has canceled - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - - fp.close() - - if self.common.platform != 'Darwin': - sys.stdout.write("\n") - - # Download is finished - if not self.stay_open: - self.download_in_progress = False - - # Close the server, if necessary - if not self.stay_open and not canceled: - print(strings._("closing_automatically")) - self.running = False - try: - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() - except: - pass - - r = Response(generate()) - r.headers.set('Content-Length', self.download_filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) - r = self.add_security_headers(r) - # guess content type - (content_type, _) = mimetypes.guess_type(basename, strict=False) - if content_type is not None: - r.headers.set('Content-Type', content_type) - return r - - def receive_routes(self): - """ - The web app routes for receiving files - """ - def index_logic(): - self.add_request(Web.REQUEST_LOAD, request.path) - - if self.common.settings.get('public_mode'): - upload_action = '/upload' - close_action = '/close' - else: - upload_action = '/{}/upload'.format(self.slug) - close_action = '/{}/close'.format(self.slug) - - r = make_response(render_template( - 'receive.html', - upload_action=upload_action, - close_action=close_action, - receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) - return self.add_security_headers(r) - - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - - def upload_logic(slug_candidate=''): - """ - Upload files. - """ - # Make sure downloads_dir exists - valid = True - try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) - - self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//upload", methods=['POST']) - def upload(slug_candidate): - self.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) - - @self.app.route("/upload", methods=['POST']) - def upload_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return upload_logic() - - - def close_logic(slug_candidate=''): - if self.common.settings.get('receive_allow_receiver_shutdown'): - self.force_shutdown() - r = make_response(render_template('closed.html')) - self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) - return self.add_security_headers(r) - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//close", methods=['POST']) - def close(slug_candidate): - self.check_slug_candidate(slug_candidate) - return close_logic(slug_candidate) - - @self.app.route("/close", methods=['POST']) - def close_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return close_logic() - - def common_routes(self): - """ - Common web app routes between sending and receiving - """ - @self.app.errorhandler(404) - def page_not_found(e): - """ - 404 error page. - """ - return self.error404() - - @self.app.route("//shutdown") - def shutdown(slug_candidate): - """ - Stop the flask web server, from the context of an http request. - """ - self.check_shutdown_slug_candidate(slug_candidate) - self.force_shutdown() - return "" - - def error404(self): - self.add_request(Web.REQUEST_OTHER, request.path) - if request.path != '/favicon.ico': - self.error404_count += 1 - - # In receive mode, with public mode enabled, skip rate limiting 404s - if not self.common.settings.get('public_mode'): - if self.error404_count == 20: - self.add_request(Web.REQUEST_RATE_LIMIT, request.path) - self.force_shutdown() - print(strings._('error_rate_limit')) - - r = make_response(render_template('404.html'), 404) - return self.add_security_headers(r) - - def add_security_headers(self, r): - """ - Add security headers to a request - """ - for header, value in self.security_headers: - r.headers.set(header, value) - return r - - def set_file_info(self, filenames, processed_size_callback=None): - """ - Using the list of filenames being shared, fill in details that the web - page will need to display. This includes zipping up the file in order to - get the zip file's name and size. - """ - self.common.log("Web", "set_file_info") - self.cancel_compression = False - - # build file info list - self.file_info = {'files': [], 'dirs': []} - for filename in filenames: - info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) - } - if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) - if os.path.isdir(filename): - info['size'] = self.common.dir_size(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['dirs'].append(info) - self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) - self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) - - # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.is_zipped = False - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] - else: - # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) - self.download_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) - # Canceling early? - if self.cancel_compression: - self.zip_writer.close() - return False - - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): - return False - - self.zip_writer.close() - self.download_filesize = os.path.getsize(self.download_filename) - self.is_zipped = True - - return True - - def _safe_select_jinja_autoescape(self, filename): - if filename is None: - return True - return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) - - def add_request(self, request_type, path, data=None): - """ - Add a request to the queue, to communicate with the GUI. - """ - self.q.put({ - 'type': request_type, - 'path': path, - 'data': data - }) - - def generate_slug(self, persistent_slug=None): - self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) - if persistent_slug != None and persistent_slug != '': - self.slug = persistent_slug - self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) - else: - self.slug = self.common.build_slug() - self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) - - def debug_mode(self): - """ - Turn on debugging mode, which will log flask errors to a debug file. - """ - temp_dir = tempfile.gettempdir() - log_handler = logging.FileHandler( - os.path.join(temp_dir, 'onionshare_server.log')) - log_handler.setLevel(logging.WARNING) - self.app.logger.addHandler(log_handler) - - def check_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if self.common.settings.get('public_mode'): - abort(404) - if not hmac.compare_digest(self.slug, slug_candidate): - abort(404) - - def check_shutdown_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if not hmac.compare_digest(self.shutdown_slug, slug_candidate): - abort(404) - - def force_shutdown(self): - """ - Stop the flask web server, from the context of the flask app. - """ - # Shutdown the flask service - try: - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('Not running with the Werkzeug Server') - func() - except: - pass - self.running = False - - def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): - """ - Start the flask web server. - """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) - if not public_mode: - self.generate_slug(persistent_slug) - - self.stay_open = stay_open - - # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) - if os.path.exists('/usr/share/anon-ws-base-files/workstation'): - host = '0.0.0.0' - else: - host = '127.0.0.1' - - self.running = True - self.app.run(host=host, port=port, threaded=True) - - def stop(self, port): - """ - Stop the flask web server by loading /shutdown. - """ - - # If the user cancels the download, let the download function know to stop - # serving the file - self.client_cancel = True - - # To stop flask, load http://127.0.0.1://shutdown - if self.running: - try: - s = socket.socket() - s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) - except: - try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() - except: - pass - - -class ZipWriter(object): - """ - ZipWriter accepts files and directories and compresses them into a zip file - with. If a zip_filename is not passed in, it will use the default onionshare - filename. - """ - def __init__(self, common, zip_filename=None, processed_size_callback=None): - self.common = common - self.cancel_compression = False - - if zip_filename: - self.zip_filename = zip_filename - else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) - - self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) - self.processed_size_callback = processed_size_callback - if self.processed_size_callback is None: - self.processed_size_callback = lambda _: None - self._size = 0 - self.processed_size_callback(self._size) - - def add_file(self, filename): - """ - Add a file to the zip archive. - """ - self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(filename) - self.processed_size_callback(self._size) - - def add_dir(self, filename): - """ - Add a directory, and all of its children, to the zip archive. - """ - dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' - for dirpath, dirnames, filenames in os.walk(filename): - for f in filenames: - # Canceling early? - if self.cancel_compression: - return False - - full_filename = os.path.join(dirpath, f) - if not os.path.islink(full_filename): - arc_filename = full_filename[len(dir_to_strip):] - self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(full_filename) - self.processed_size_callback(self._size) - - return True - - def close(self): - """ - Close the zip archive. - """ - self.z.close() - - -class ReceiveModeWSGIMiddleware(object): - """ - Custom WSGI middleware in order to attach the Web object to environ, so - ReceiveModeRequest can access it. - """ - def __init__(self, app, web): - self.app = app - self.web = web - - def __call__(self, environ, start_response): - environ['web'] = self.web - return self.app(environ, start_response) - - -class ReceiveModeTemporaryFile(object): - """ - A custom TemporaryFile that tells ReceiveModeRequest every time data gets - written to it, in order to track the progress of uploads. - """ - def __init__(self, filename, write_func, close_func): - self.onionshare_filename = filename - self.onionshare_write_func = write_func - self.onionshare_close_func = close_func - - # Create a temporary file - self.f = tempfile.TemporaryFile('wb+') - - # Make all the file-like methods and attributes actually access the - # TemporaryFile, except for write - attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', - 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', - 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', - 'truncate', 'writable', 'writelines'] - for attr in attrs: - setattr(self, attr, getattr(self.f, attr)) - - def write(self, b): - """ - Custom write method that calls out to onionshare_write_func - """ - bytes_written = self.f.write(b) - self.onionshare_write_func(self.onionshare_filename, bytes_written) - - def close(self): - """ - Custom close method that calls out to onionshare_close_func - """ - self.f.close() - self.onionshare_close_func(self.onionshare_filename) - - -class ReceiveModeRequest(Request): - """ - A custom flask Request object that keeps track of how much data has been - uploaded for each file, for receive mode. - """ - def __init__(self, environ, populate_request=True, shallow=False): - super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) - self.web = environ['web'] - - # Is this a valid upload request? - self.upload_request = False - if self.method == 'POST': - if self.path == '/{}/upload'.format(self.web.slug): - self.upload_request = True - else: - if self.web.common.settings.get('public_mode'): - if self.path == '/upload': - self.upload_request = True - - if self.upload_request: - # A dictionary that maps filenames to the bytes uploaded so far - self.progress = {} - - # Create an upload_id, attach it to the request - self.upload_id = self.web.upload_count - self.web.upload_count += 1 - - # Figure out the content length - try: - self.content_length = int(self.headers['Content-Length']) - except: - self.content_length = 0 - - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) - - # Tell the GUI - self.web.add_request(Web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, - 'content_length': self.content_length - }) - - self.previous_file = None - - def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): - """ - This gets called for each file that gets uploaded, and returns an file-like - writable stream. - """ - if self.upload_request: - self.progress[filename] = { - 'uploaded_bytes': 0, - 'complete': False - } - - return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) - - def close(self): - """ - Closing the request. - """ - super(ReceiveModeRequest, self).close() - if self.upload_request: - # Inform the GUI that the upload has finished - self.web.add_request(Web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': self.upload_id - }) - - def file_write_func(self, filename, length): - """ - This function gets called when a specific file is written to. - """ - if self.upload_request: - self.progress[filename]['uploaded_bytes'] += length - - if self.previous_file != filename: - if self.previous_file is not None: - print('') - self.previous_file = filename - - print('\r=> {:15s} {}'.format( - self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), - filename - ), end='') - - # Update the GUI on the upload progress - self.web.add_request(Web.REQUEST_PROGRESS, self.path, { - 'id': self.upload_id, - 'progress': self.progress - }) - - def file_close_func(self, filename): - """ - This function gets called when a specific file is closed. - """ - self.progress[filename]['complete'] = True diff --git a/onionshare/web/__init__.py b/onionshare/web/__init__.py new file mode 100644 index 00000000..5a7b297f --- /dev/null +++ b/onionshare/web/__init__.py @@ -0,0 +1,876 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import hmac +import logging +import mimetypes +import os +import queue +import socket +import sys +import tempfile +import zipfile +import re +import io +from distutils.version import LooseVersion as Version +from urllib.request import urlopen +from datetime import datetime + +import flask +from flask import ( + Flask, Response, Request, request, render_template, abort, make_response, + flash, redirect, __version__ as flask_version +) +from werkzeug.utils import secure_filename + +from .. import strings +from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable + + +# Stub out flask's show_server_banner function, to avoiding showing warnings that +# are not applicable to OnionShare +def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): + pass + +flask.cli.show_server_banner = stubbed_show_server_banner + + +class Web(object): + """ + The Web object is the OnionShare web server, powered by flask + """ + REQUEST_LOAD = 0 + REQUEST_STARTED = 1 + REQUEST_PROGRESS = 2 + REQUEST_OTHER = 3 + REQUEST_CANCELED = 4 + REQUEST_RATE_LIMIT = 5 + REQUEST_CLOSE_SERVER = 6 + REQUEST_UPLOAD_FILE_RENAMED = 7 + REQUEST_UPLOAD_FINISHED = 8 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 + + def __init__(self, common, gui_mode, receive_mode=False): + self.common = common + + # The flask app + self.app = Flask(__name__, + static_folder=self.common.get_resource_path('static'), + template_folder=self.common.get_resource_path('templates')) + self.app.secret_key = self.common.random_string(8) + + # Debug mode? + if self.common.debug: + self.debug_mode() + + # Are we running in GUI mode? + self.gui_mode = gui_mode + + # Are we using receive mode? + self.receive_mode = receive_mode + if self.receive_mode: + # Use custom WSGI middleware, to modify environ + self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) + # Use a custom Request class to track upload progess + self.app.request_class = ReceiveModeRequest + + # Starting in Flask 0.11, render_template_string autoescapes template variables + # by default. To prevent content injection through template variables in + # earlier versions of Flask, we force autoescaping in the Jinja2 template + # engine if we detect a Flask version with insecure default behavior. + if Version(flask_version) < Version('0.11'): + # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc + Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape + + # Information about the file + self.file_info = [] + self.is_zipped = False + self.download_filename = None + self.download_filesize = None + self.zip_writer = None + + self.security_headers = [ + ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'OnionShare') + ] + + self.q = queue.Queue() + + self.slug = None + + self.download_count = 0 + self.upload_count = 0 + + self.error404_count = 0 + + # If "Stop After First Download" is checked (stay_open == False), only allow + # one download at a time. + self.download_in_progress = False + + self.done = False + + # If the client closes the OnionShare window while a download is in progress, + # it should immediately stop serving the file. The client_cancel global is + # used to tell the download function that the client is canceling the download. + self.client_cancel = False + + # shutting down the server only works within the context of flask, so the easiest way to do it is over http + self.shutdown_slug = self.common.random_string(16) + + # Keep track if the server is running + self.running = False + + # Define the ewb app routes + self.common_routes() + if self.receive_mode: + self.receive_routes() + else: + self.send_routes() + + def send_routes(self): + """ + The web app routes for sharing files + """ + @self.app.route("/") + def index(slug_candidate): + self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return index_logic() + + def index_logic(slug_candidate=''): + """ + Render the template for the onionshare landing page. + """ + self.add_request(Web.REQUEST_LOAD, request.path) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) + + # If download is allowed to continue, serve download page + if self.slug: + r = make_response(render_template( + 'send.html', + slug=self.slug, + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) + else: + # If download is allowed to continue, serve download page + r = make_response(render_template( + 'send.html', + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) + return self.add_security_headers(r) + + @self.app.route("//download") + def download(slug_candidate): + self.check_slug_candidate(slug_candidate) + return download_logic() + + @self.app.route("/download") + def download_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return download_logic() + + def download_logic(slug_candidate=''): + """ + Download the zip file. + """ + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) + + # Each download has a unique id + download_id = self.download_count + self.download_count += 1 + + # Prepare some variables to use inside generate() function below + # which is outside of the request context + shutdown_func = request.environ.get('werkzeug.server.shutdown') + path = request.path + + # Tell GUI the download started + self.add_request(Web.REQUEST_STARTED, path, { + 'id': download_id} + ) + + dirname = os.path.dirname(self.download_filename) + basename = os.path.basename(self.download_filename) + + def generate(): + # The user hasn't canceled the download + self.client_cancel = False + + # Starting a new download + if not self.stay_open: + self.download_in_progress = True + + chunk_size = 102400 # 100kb + + fp = open(self.download_filename, 'rb') + self.done = False + canceled = False + while not self.done: + # The user has canceled the download, so stop serving the file + if self.client_cancel: + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) + break + + chunk = fp.read(chunk_size) + if chunk == b'': + self.done = True + else: + try: + yield chunk + + # tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 + + # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) + if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + self.add_request(Web.REQUEST_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) + self.done = False + except: + # looks like the download was canceled + self.done = True + canceled = True + + # tell the GUI the download has canceled + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) + + fp.close() + + if self.common.platform != 'Darwin': + sys.stdout.write("\n") + + # Download is finished + if not self.stay_open: + self.download_in_progress = False + + # Close the server, if necessary + if not self.stay_open and not canceled: + print(strings._("closing_automatically")) + self.running = False + try: + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() + except: + pass + + r = Response(generate()) + r.headers.set('Content-Length', self.download_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + r = self.add_security_headers(r) + # guess content type + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r + + def receive_routes(self): + """ + The web app routes for receiving files + """ + def index_logic(): + self.add_request(Web.REQUEST_LOAD, request.path) + + if self.common.settings.get('public_mode'): + upload_action = '/upload' + close_action = '/close' + else: + upload_action = '/{}/upload'.format(self.slug) + close_action = '/{}/close'.format(self.slug) + + r = make_response(render_template( + 'receive.html', + upload_action=upload_action, + close_action=close_action, + receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) + return self.add_security_headers(r) + + @self.app.route("/") + def index(slug_candidate): + self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return index_logic() + + + def upload_logic(slug_candidate=''): + """ + Upload files. + """ + # Make sure downloads_dir exists + valid = True + try: + self.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') + + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + @self.app.route("//upload", methods=['POST']) + def upload(slug_candidate): + self.check_slug_candidate(slug_candidate) + return upload_logic(slug_candidate) + + @self.app.route("/upload", methods=['POST']) + def upload_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return upload_logic() + + + def close_logic(slug_candidate=''): + if self.common.settings.get('receive_allow_receiver_shutdown'): + self.force_shutdown() + r = make_response(render_template('closed.html')) + self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) + return self.add_security_headers(r) + else: + return redirect('/{}'.format(slug_candidate)) + + @self.app.route("//close", methods=['POST']) + def close(slug_candidate): + self.check_slug_candidate(slug_candidate) + return close_logic(slug_candidate) + + @self.app.route("/close", methods=['POST']) + def close_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return close_logic() + + def common_routes(self): + """ + Common web app routes between sending and receiving + """ + @self.app.errorhandler(404) + def page_not_found(e): + """ + 404 error page. + """ + return self.error404() + + @self.app.route("//shutdown") + def shutdown(slug_candidate): + """ + Stop the flask web server, from the context of an http request. + """ + self.check_shutdown_slug_candidate(slug_candidate) + self.force_shutdown() + return "" + + def error404(self): + self.add_request(Web.REQUEST_OTHER, request.path) + if request.path != '/favicon.ico': + self.error404_count += 1 + + # In receive mode, with public mode enabled, skip rate limiting 404s + if not self.common.settings.get('public_mode'): + if self.error404_count == 20: + self.add_request(Web.REQUEST_RATE_LIMIT, request.path) + self.force_shutdown() + print(strings._('error_rate_limit')) + + r = make_response(render_template('404.html'), 404) + return self.add_security_headers(r) + + def add_security_headers(self, r): + """ + Add security headers to a request + """ + for header, value in self.security_headers: + r.headers.set(header, value) + return r + + def set_file_info(self, filenames, processed_size_callback=None): + """ + Using the list of filenames being shared, fill in details that the web + page will need to display. This includes zipping up the file in order to + get the zip file's name and size. + """ + self.common.log("Web", "set_file_info") + self.cancel_compression = False + + # build file info list + self.file_info = {'files': [], 'dirs': []} + for filename in filenames: + info = { + 'filename': filename, + 'basename': os.path.basename(filename.rstrip('/')) + } + if os.path.isfile(filename): + info['size'] = os.path.getsize(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['files'].append(info) + if os.path.isdir(filename): + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['dirs'].append(info) + self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) + self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + + # Check if there's only 1 file and no folders + if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: + self.is_zipped = False + self.download_filename = self.file_info['files'][0]['filename'] + self.download_filesize = self.file_info['files'][0]['size'] + else: + # Zip up the files and folders + self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.download_filename = self.zip_writer.zip_filename + for info in self.file_info['files']: + self.zip_writer.add_file(info['filename']) + # Canceling early? + if self.cancel_compression: + self.zip_writer.close() + return False + + for info in self.file_info['dirs']: + if not self.zip_writer.add_dir(info['filename']): + return False + + self.zip_writer.close() + self.download_filesize = os.path.getsize(self.download_filename) + self.is_zipped = True + + return True + + def _safe_select_jinja_autoescape(self, filename): + if filename is None: + return True + return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + + def add_request(self, request_type, path, data=None): + """ + Add a request to the queue, to communicate with the GUI. + """ + self.q.put({ + 'type': request_type, + 'path': path, + 'data': data + }) + + def generate_slug(self, persistent_slug=None): + self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) + if persistent_slug != None and persistent_slug != '': + self.slug = persistent_slug + self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) + else: + self.slug = self.common.build_slug() + self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) + + def debug_mode(self): + """ + Turn on debugging mode, which will log flask errors to a debug file. + """ + temp_dir = tempfile.gettempdir() + log_handler = logging.FileHandler( + os.path.join(temp_dir, 'onionshare_server.log')) + log_handler.setLevel(logging.WARNING) + self.app.logger.addHandler(log_handler) + + def check_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) + if self.common.settings.get('public_mode'): + abort(404) + if not hmac.compare_digest(self.slug, slug_candidate): + abort(404) + + def check_shutdown_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) + if not hmac.compare_digest(self.shutdown_slug, slug_candidate): + abort(404) + + def force_shutdown(self): + """ + Stop the flask web server, from the context of the flask app. + """ + # Shutdown the flask service + try: + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + except: + pass + self.running = False + + def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): + """ + Start the flask web server. + """ + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) + if not public_mode: + self.generate_slug(persistent_slug) + + self.stay_open = stay_open + + # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) + if os.path.exists('/usr/share/anon-ws-base-files/workstation'): + host = '0.0.0.0' + else: + host = '127.0.0.1' + + self.running = True + self.app.run(host=host, port=port, threaded=True) + + def stop(self, port): + """ + Stop the flask web server by loading /shutdown. + """ + + # If the user cancels the download, let the download function know to stop + # serving the file + self.client_cancel = True + + # To stop flask, load http://127.0.0.1://shutdown + if self.running: + try: + s = socket.socket() + s.connect(('127.0.0.1', port)) + s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) + except: + try: + urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() + except: + pass + + +class ZipWriter(object): + """ + ZipWriter accepts files and directories and compresses them into a zip file + with. If a zip_filename is not passed in, it will use the default onionshare + filename. + """ + def __init__(self, common, zip_filename=None, processed_size_callback=None): + self.common = common + self.cancel_compression = False + + if zip_filename: + self.zip_filename = zip_filename + else: + self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) + + self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) + self.processed_size_callback = processed_size_callback + if self.processed_size_callback is None: + self.processed_size_callback = lambda _: None + self._size = 0 + self.processed_size_callback(self._size) + + def add_file(self, filename): + """ + Add a file to the zip archive. + """ + self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(filename) + self.processed_size_callback(self._size) + + def add_dir(self, filename): + """ + Add a directory, and all of its children, to the zip archive. + """ + dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' + for dirpath, dirnames, filenames in os.walk(filename): + for f in filenames: + # Canceling early? + if self.cancel_compression: + return False + + full_filename = os.path.join(dirpath, f) + if not os.path.islink(full_filename): + arc_filename = full_filename[len(dir_to_strip):] + self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(full_filename) + self.processed_size_callback(self._size) + + return True + + def close(self): + """ + Close the zip archive. + """ + self.z.close() + + +class ReceiveModeWSGIMiddleware(object): + """ + Custom WSGI middleware in order to attach the Web object to environ, so + ReceiveModeRequest can access it. + """ + def __init__(self, app, web): + self.app = app + self.web = web + + def __call__(self, environ, start_response): + environ['web'] = self.web + return self.app(environ, start_response) + + +class ReceiveModeTemporaryFile(object): + """ + A custom TemporaryFile that tells ReceiveModeRequest every time data gets + written to it, in order to track the progress of uploads. + """ + def __init__(self, filename, write_func, close_func): + self.onionshare_filename = filename + self.onionshare_write_func = write_func + self.onionshare_close_func = close_func + + # Create a temporary file + self.f = tempfile.TemporaryFile('wb+') + + # Make all the file-like methods and attributes actually access the + # TemporaryFile, except for write + attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', + 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', + 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', + 'truncate', 'writable', 'writelines'] + for attr in attrs: + setattr(self, attr, getattr(self.f, attr)) + + def write(self, b): + """ + Custom write method that calls out to onionshare_write_func + """ + bytes_written = self.f.write(b) + self.onionshare_write_func(self.onionshare_filename, bytes_written) + + def close(self): + """ + Custom close method that calls out to onionshare_close_func + """ + self.f.close() + self.onionshare_close_func(self.onionshare_filename) + + +class ReceiveModeRequest(Request): + """ + A custom flask Request object that keeps track of how much data has been + uploaded for each file, for receive mode. + """ + def __init__(self, environ, populate_request=True, shallow=False): + super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) + self.web = environ['web'] + + # Is this a valid upload request? + self.upload_request = False + if self.method == 'POST': + if self.path == '/{}/upload'.format(self.web.slug): + self.upload_request = True + else: + if self.web.common.settings.get('public_mode'): + if self.path == '/upload': + self.upload_request = True + + if self.upload_request: + # A dictionary that maps filenames to the bytes uploaded so far + self.progress = {} + + # Create an upload_id, attach it to the request + self.upload_id = self.web.upload_count + self.web.upload_count += 1 + + # Figure out the content length + try: + self.content_length = int(self.headers['Content-Length']) + except: + self.content_length = 0 + + print("{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) + )) + + # Tell the GUI + self.web.add_request(Web.REQUEST_STARTED, self.path, { + 'id': self.upload_id, + 'content_length': self.content_length + }) + + self.previous_file = None + + def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): + """ + This gets called for each file that gets uploaded, and returns an file-like + writable stream. + """ + if self.upload_request: + self.progress[filename] = { + 'uploaded_bytes': 0, + 'complete': False + } + + return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) + + def close(self): + """ + Closing the request. + """ + super(ReceiveModeRequest, self).close() + if self.upload_request: + # Inform the GUI that the upload has finished + self.web.add_request(Web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': self.upload_id + }) + + def file_write_func(self, filename, length): + """ + This function gets called when a specific file is written to. + """ + if self.upload_request: + self.progress[filename]['uploaded_bytes'] += length + + if self.previous_file != filename: + if self.previous_file is not None: + print('') + self.previous_file = filename + + print('\r=> {:15s} {}'.format( + self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), + filename + ), end='') + + # Update the GUI on the upload progress + self.web.add_request(Web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'progress': self.progress + }) + + def file_close_func(self, filename): + """ + This function gets called when a specific file is closed. + """ + self.progress[filename]['complete'] = True -- cgit v1.2.3-54-g00ecf From 71ea9bf29edee513f456d6353d5e716e33ae75f7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:43:04 -0700 Subject: Split the web module into different files for receive mode and share mode logic --- onionshare/web/__init__.py | 857 +---------------------------------------- onionshare/web/receive_mode.py | 156 ++++++++ onionshare/web/share_mode.py | 60 +++ onionshare/web/web.py | 647 +++++++++++++++++++++++++++++++ 4 files changed, 864 insertions(+), 856 deletions(-) create mode 100644 onionshare/web/receive_mode.py create mode 100644 onionshare/web/share_mode.py create mode 100644 onionshare/web/web.py diff --git a/onionshare/web/__init__.py b/onionshare/web/__init__.py index 5a7b297f..d45b4983 100644 --- a/onionshare/web/__init__.py +++ b/onionshare/web/__init__.py @@ -18,859 +18,4 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import hmac -import logging -import mimetypes -import os -import queue -import socket -import sys -import tempfile -import zipfile -import re -import io -from distutils.version import LooseVersion as Version -from urllib.request import urlopen -from datetime import datetime - -import flask -from flask import ( - Flask, Response, Request, request, render_template, abort, make_response, - flash, redirect, __version__ as flask_version -) -from werkzeug.utils import secure_filename - -from .. import strings -from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable - - -# Stub out flask's show_server_banner function, to avoiding showing warnings that -# are not applicable to OnionShare -def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): - pass - -flask.cli.show_server_banner = stubbed_show_server_banner - - -class Web(object): - """ - The Web object is the OnionShare web server, powered by flask - """ - REQUEST_LOAD = 0 - REQUEST_STARTED = 1 - REQUEST_PROGRESS = 2 - REQUEST_OTHER = 3 - REQUEST_CANCELED = 4 - REQUEST_RATE_LIMIT = 5 - REQUEST_CLOSE_SERVER = 6 - REQUEST_UPLOAD_FILE_RENAMED = 7 - REQUEST_UPLOAD_FINISHED = 8 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 - REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 - - def __init__(self, common, gui_mode, receive_mode=False): - self.common = common - - # The flask app - self.app = Flask(__name__, - static_folder=self.common.get_resource_path('static'), - template_folder=self.common.get_resource_path('templates')) - self.app.secret_key = self.common.random_string(8) - - # Debug mode? - if self.common.debug: - self.debug_mode() - - # Are we running in GUI mode? - self.gui_mode = gui_mode - - # Are we using receive mode? - self.receive_mode = receive_mode - if self.receive_mode: - # Use custom WSGI middleware, to modify environ - self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) - # Use a custom Request class to track upload progess - self.app.request_class = ReceiveModeRequest - - # Starting in Flask 0.11, render_template_string autoescapes template variables - # by default. To prevent content injection through template variables in - # earlier versions of Flask, we force autoescaping in the Jinja2 template - # engine if we detect a Flask version with insecure default behavior. - if Version(flask_version) < Version('0.11'): - # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc - Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape - - # Information about the file - self.file_info = [] - self.is_zipped = False - self.download_filename = None - self.download_filesize = None - self.zip_writer = None - - self.security_headers = [ - ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), - ('X-Frame-Options', 'DENY'), - ('X-Xss-Protection', '1; mode=block'), - ('X-Content-Type-Options', 'nosniff'), - ('Referrer-Policy', 'no-referrer'), - ('Server', 'OnionShare') - ] - - self.q = queue.Queue() - - self.slug = None - - self.download_count = 0 - self.upload_count = 0 - - self.error404_count = 0 - - # If "Stop After First Download" is checked (stay_open == False), only allow - # one download at a time. - self.download_in_progress = False - - self.done = False - - # If the client closes the OnionShare window while a download is in progress, - # it should immediately stop serving the file. The client_cancel global is - # used to tell the download function that the client is canceling the download. - self.client_cancel = False - - # shutting down the server only works within the context of flask, so the easiest way to do it is over http - self.shutdown_slug = self.common.random_string(16) - - # Keep track if the server is running - self.running = False - - # Define the ewb app routes - self.common_routes() - if self.receive_mode: - self.receive_routes() - else: - self.send_routes() - - def send_routes(self): - """ - The web app routes for sharing files - """ - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - def index_logic(slug_candidate=''): - """ - Render the template for the onionshare landing page. - """ - self.add_request(Web.REQUEST_LOAD, request.path) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # If download is allowed to continue, serve download page - if self.slug: - r = make_response(render_template( - 'send.html', - slug=self.slug, - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - else: - # If download is allowed to continue, serve download page - r = make_response(render_template( - 'send.html', - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - return self.add_security_headers(r) - - @self.app.route("//download") - def download(slug_candidate): - self.check_slug_candidate(slug_candidate) - return download_logic() - - @self.app.route("/download") - def download_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return download_logic() - - def download_logic(slug_candidate=''): - """ - Download the zip file. - """ - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # Each download has a unique id - download_id = self.download_count - self.download_count += 1 - - # Prepare some variables to use inside generate() function below - # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') - path = request.path - - # Tell GUI the download started - self.add_request(Web.REQUEST_STARTED, path, { - 'id': download_id} - ) - - dirname = os.path.dirname(self.download_filename) - basename = os.path.basename(self.download_filename) - - def generate(): - # The user hasn't canceled the download - self.client_cancel = False - - # Starting a new download - if not self.stay_open: - self.download_in_progress = True - - chunk_size = 102400 # 100kb - - fp = open(self.download_filename, 'rb') - self.done = False - canceled = False - while not self.done: - # The user has canceled the download, so stop serving the file - if self.client_cancel: - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - break - - chunk = fp.read(chunk_size) - if chunk == b'': - self.done = True - else: - try: - yield chunk - - # tell GUI the progress - downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 - - # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': - sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) - sys.stdout.flush() - - self.add_request(Web.REQUEST_PROGRESS, path, { - 'id': download_id, - 'bytes': downloaded_bytes - }) - self.done = False - except: - # looks like the download was canceled - self.done = True - canceled = True - - # tell the GUI the download has canceled - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - - fp.close() - - if self.common.platform != 'Darwin': - sys.stdout.write("\n") - - # Download is finished - if not self.stay_open: - self.download_in_progress = False - - # Close the server, if necessary - if not self.stay_open and not canceled: - print(strings._("closing_automatically")) - self.running = False - try: - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() - except: - pass - - r = Response(generate()) - r.headers.set('Content-Length', self.download_filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) - r = self.add_security_headers(r) - # guess content type - (content_type, _) = mimetypes.guess_type(basename, strict=False) - if content_type is not None: - r.headers.set('Content-Type', content_type) - return r - - def receive_routes(self): - """ - The web app routes for receiving files - """ - def index_logic(): - self.add_request(Web.REQUEST_LOAD, request.path) - - if self.common.settings.get('public_mode'): - upload_action = '/upload' - close_action = '/close' - else: - upload_action = '/{}/upload'.format(self.slug) - close_action = '/{}/close'.format(self.slug) - - r = make_response(render_template( - 'receive.html', - upload_action=upload_action, - close_action=close_action, - receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) - return self.add_security_headers(r) - - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - - def upload_logic(slug_candidate=''): - """ - Upload files. - """ - # Make sure downloads_dir exists - valid = True - try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) - - self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//upload", methods=['POST']) - def upload(slug_candidate): - self.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) - - @self.app.route("/upload", methods=['POST']) - def upload_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return upload_logic() - - - def close_logic(slug_candidate=''): - if self.common.settings.get('receive_allow_receiver_shutdown'): - self.force_shutdown() - r = make_response(render_template('closed.html')) - self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) - return self.add_security_headers(r) - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//close", methods=['POST']) - def close(slug_candidate): - self.check_slug_candidate(slug_candidate) - return close_logic(slug_candidate) - - @self.app.route("/close", methods=['POST']) - def close_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return close_logic() - - def common_routes(self): - """ - Common web app routes between sending and receiving - """ - @self.app.errorhandler(404) - def page_not_found(e): - """ - 404 error page. - """ - return self.error404() - - @self.app.route("//shutdown") - def shutdown(slug_candidate): - """ - Stop the flask web server, from the context of an http request. - """ - self.check_shutdown_slug_candidate(slug_candidate) - self.force_shutdown() - return "" - - def error404(self): - self.add_request(Web.REQUEST_OTHER, request.path) - if request.path != '/favicon.ico': - self.error404_count += 1 - - # In receive mode, with public mode enabled, skip rate limiting 404s - if not self.common.settings.get('public_mode'): - if self.error404_count == 20: - self.add_request(Web.REQUEST_RATE_LIMIT, request.path) - self.force_shutdown() - print(strings._('error_rate_limit')) - - r = make_response(render_template('404.html'), 404) - return self.add_security_headers(r) - - def add_security_headers(self, r): - """ - Add security headers to a request - """ - for header, value in self.security_headers: - r.headers.set(header, value) - return r - - def set_file_info(self, filenames, processed_size_callback=None): - """ - Using the list of filenames being shared, fill in details that the web - page will need to display. This includes zipping up the file in order to - get the zip file's name and size. - """ - self.common.log("Web", "set_file_info") - self.cancel_compression = False - - # build file info list - self.file_info = {'files': [], 'dirs': []} - for filename in filenames: - info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) - } - if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) - if os.path.isdir(filename): - info['size'] = self.common.dir_size(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['dirs'].append(info) - self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) - self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) - - # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.is_zipped = False - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] - else: - # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) - self.download_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) - # Canceling early? - if self.cancel_compression: - self.zip_writer.close() - return False - - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): - return False - - self.zip_writer.close() - self.download_filesize = os.path.getsize(self.download_filename) - self.is_zipped = True - - return True - - def _safe_select_jinja_autoescape(self, filename): - if filename is None: - return True - return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) - - def add_request(self, request_type, path, data=None): - """ - Add a request to the queue, to communicate with the GUI. - """ - self.q.put({ - 'type': request_type, - 'path': path, - 'data': data - }) - - def generate_slug(self, persistent_slug=None): - self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) - if persistent_slug != None and persistent_slug != '': - self.slug = persistent_slug - self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) - else: - self.slug = self.common.build_slug() - self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) - - def debug_mode(self): - """ - Turn on debugging mode, which will log flask errors to a debug file. - """ - temp_dir = tempfile.gettempdir() - log_handler = logging.FileHandler( - os.path.join(temp_dir, 'onionshare_server.log')) - log_handler.setLevel(logging.WARNING) - self.app.logger.addHandler(log_handler) - - def check_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if self.common.settings.get('public_mode'): - abort(404) - if not hmac.compare_digest(self.slug, slug_candidate): - abort(404) - - def check_shutdown_slug_candidate(self, slug_candidate): - self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) - if not hmac.compare_digest(self.shutdown_slug, slug_candidate): - abort(404) - - def force_shutdown(self): - """ - Stop the flask web server, from the context of the flask app. - """ - # Shutdown the flask service - try: - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('Not running with the Werkzeug Server') - func() - except: - pass - self.running = False - - def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): - """ - Start the flask web server. - """ - self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) - if not public_mode: - self.generate_slug(persistent_slug) - - self.stay_open = stay_open - - # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) - if os.path.exists('/usr/share/anon-ws-base-files/workstation'): - host = '0.0.0.0' - else: - host = '127.0.0.1' - - self.running = True - self.app.run(host=host, port=port, threaded=True) - - def stop(self, port): - """ - Stop the flask web server by loading /shutdown. - """ - - # If the user cancels the download, let the download function know to stop - # serving the file - self.client_cancel = True - - # To stop flask, load http://127.0.0.1://shutdown - if self.running: - try: - s = socket.socket() - s.connect(('127.0.0.1', port)) - s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) - except: - try: - urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() - except: - pass - - -class ZipWriter(object): - """ - ZipWriter accepts files and directories and compresses them into a zip file - with. If a zip_filename is not passed in, it will use the default onionshare - filename. - """ - def __init__(self, common, zip_filename=None, processed_size_callback=None): - self.common = common - self.cancel_compression = False - - if zip_filename: - self.zip_filename = zip_filename - else: - self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) - - self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) - self.processed_size_callback = processed_size_callback - if self.processed_size_callback is None: - self.processed_size_callback = lambda _: None - self._size = 0 - self.processed_size_callback(self._size) - - def add_file(self, filename): - """ - Add a file to the zip archive. - """ - self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(filename) - self.processed_size_callback(self._size) - - def add_dir(self, filename): - """ - Add a directory, and all of its children, to the zip archive. - """ - dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' - for dirpath, dirnames, filenames in os.walk(filename): - for f in filenames: - # Canceling early? - if self.cancel_compression: - return False - - full_filename = os.path.join(dirpath, f) - if not os.path.islink(full_filename): - arc_filename = full_filename[len(dir_to_strip):] - self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) - self._size += os.path.getsize(full_filename) - self.processed_size_callback(self._size) - - return True - - def close(self): - """ - Close the zip archive. - """ - self.z.close() - - -class ReceiveModeWSGIMiddleware(object): - """ - Custom WSGI middleware in order to attach the Web object to environ, so - ReceiveModeRequest can access it. - """ - def __init__(self, app, web): - self.app = app - self.web = web - - def __call__(self, environ, start_response): - environ['web'] = self.web - return self.app(environ, start_response) - - -class ReceiveModeTemporaryFile(object): - """ - A custom TemporaryFile that tells ReceiveModeRequest every time data gets - written to it, in order to track the progress of uploads. - """ - def __init__(self, filename, write_func, close_func): - self.onionshare_filename = filename - self.onionshare_write_func = write_func - self.onionshare_close_func = close_func - - # Create a temporary file - self.f = tempfile.TemporaryFile('wb+') - - # Make all the file-like methods and attributes actually access the - # TemporaryFile, except for write - attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', - 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', - 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', - 'truncate', 'writable', 'writelines'] - for attr in attrs: - setattr(self, attr, getattr(self.f, attr)) - - def write(self, b): - """ - Custom write method that calls out to onionshare_write_func - """ - bytes_written = self.f.write(b) - self.onionshare_write_func(self.onionshare_filename, bytes_written) - - def close(self): - """ - Custom close method that calls out to onionshare_close_func - """ - self.f.close() - self.onionshare_close_func(self.onionshare_filename) - - -class ReceiveModeRequest(Request): - """ - A custom flask Request object that keeps track of how much data has been - uploaded for each file, for receive mode. - """ - def __init__(self, environ, populate_request=True, shallow=False): - super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) - self.web = environ['web'] - - # Is this a valid upload request? - self.upload_request = False - if self.method == 'POST': - if self.path == '/{}/upload'.format(self.web.slug): - self.upload_request = True - else: - if self.web.common.settings.get('public_mode'): - if self.path == '/upload': - self.upload_request = True - - if self.upload_request: - # A dictionary that maps filenames to the bytes uploaded so far - self.progress = {} - - # Create an upload_id, attach it to the request - self.upload_id = self.web.upload_count - self.web.upload_count += 1 - - # Figure out the content length - try: - self.content_length = int(self.headers['Content-Length']) - except: - self.content_length = 0 - - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) - - # Tell the GUI - self.web.add_request(Web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, - 'content_length': self.content_length - }) - - self.previous_file = None - - def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): - """ - This gets called for each file that gets uploaded, and returns an file-like - writable stream. - """ - if self.upload_request: - self.progress[filename] = { - 'uploaded_bytes': 0, - 'complete': False - } - - return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) - - def close(self): - """ - Closing the request. - """ - super(ReceiveModeRequest, self).close() - if self.upload_request: - # Inform the GUI that the upload has finished - self.web.add_request(Web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': self.upload_id - }) - - def file_write_func(self, filename, length): - """ - This function gets called when a specific file is written to. - """ - if self.upload_request: - self.progress[filename]['uploaded_bytes'] += length - - if self.previous_file != filename: - if self.previous_file is not None: - print('') - self.previous_file = filename - - print('\r=> {:15s} {}'.format( - self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), - filename - ), end='') - - # Update the GUI on the upload progress - self.web.add_request(Web.REQUEST_PROGRESS, self.path, { - 'id': self.upload_id, - 'progress': self.progress - }) - - def file_close_func(self, filename): - """ - This function gets called when a specific file is closed. - """ - self.progress[filename]['complete'] = True +from .web import Web diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py new file mode 100644 index 00000000..90accc8c --- /dev/null +++ b/onionshare/web/receive_mode.py @@ -0,0 +1,156 @@ +import tempfile +from datetime import datetime +from flask import Request + +from .. import strings + + +class ReceiveModeWSGIMiddleware(object): + """ + Custom WSGI middleware in order to attach the Web object to environ, so + ReceiveModeRequest can access it. + """ + def __init__(self, app, web): + self.app = app + self.web = web + + def __call__(self, environ, start_response): + environ['web'] = self.web + return self.app(environ, start_response) + + +class ReceiveModeTemporaryFile(object): + """ + A custom TemporaryFile that tells ReceiveModeRequest every time data gets + written to it, in order to track the progress of uploads. + """ + def __init__(self, filename, write_func, close_func): + self.onionshare_filename = filename + self.onionshare_write_func = write_func + self.onionshare_close_func = close_func + + # Create a temporary file + self.f = tempfile.TemporaryFile('wb+') + + # Make all the file-like methods and attributes actually access the + # TemporaryFile, except for write + attrs = ['closed', 'detach', 'fileno', 'flush', 'isatty', 'mode', + 'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto', + 'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell', + 'truncate', 'writable', 'writelines'] + for attr in attrs: + setattr(self, attr, getattr(self.f, attr)) + + def write(self, b): + """ + Custom write method that calls out to onionshare_write_func + """ + bytes_written = self.f.write(b) + self.onionshare_write_func(self.onionshare_filename, bytes_written) + + def close(self): + """ + Custom close method that calls out to onionshare_close_func + """ + self.f.close() + self.onionshare_close_func(self.onionshare_filename) + + +class ReceiveModeRequest(Request): + """ + A custom flask Request object that keeps track of how much data has been + uploaded for each file, for receive mode. + """ + def __init__(self, environ, populate_request=True, shallow=False): + super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) + self.web = environ['web'] + + # Is this a valid upload request? + self.upload_request = False + if self.method == 'POST': + if self.path == '/{}/upload'.format(self.web.slug): + self.upload_request = True + else: + if self.web.common.settings.get('public_mode'): + if self.path == '/upload': + self.upload_request = True + + if self.upload_request: + # A dictionary that maps filenames to the bytes uploaded so far + self.progress = {} + + # Create an upload_id, attach it to the request + self.upload_id = self.web.upload_count + self.web.upload_count += 1 + + # Figure out the content length + try: + self.content_length = int(self.headers['Content-Length']) + except: + self.content_length = 0 + + print("{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) + )) + + # Tell the GUI + self.web.add_request(self.web.REQUEST_STARTED, self.path, { + 'id': self.upload_id, + 'content_length': self.content_length + }) + + self.previous_file = None + + def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): + """ + This gets called for each file that gets uploaded, and returns an file-like + writable stream. + """ + if self.upload_request: + self.progress[filename] = { + 'uploaded_bytes': 0, + 'complete': False + } + + return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) + + def close(self): + """ + Closing the request. + """ + super(ReceiveModeRequest, self).close() + if self.upload_request: + # Inform the GUI that the upload has finished + self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': self.upload_id + }) + + def file_write_func(self, filename, length): + """ + This function gets called when a specific file is written to. + """ + if self.upload_request: + self.progress[filename]['uploaded_bytes'] += length + + if self.previous_file != filename: + if self.previous_file is not None: + print('') + self.previous_file = filename + + print('\r=> {:15s} {}'.format( + self.web.common.human_readable_filesize(self.progress[filename]['uploaded_bytes']), + filename + ), end='') + + # Update the GUI on the upload progress + self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'progress': self.progress + }) + + def file_close_func(self, filename): + """ + This function gets called when a specific file is closed. + """ + self.progress[filename]['complete'] = True diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py new file mode 100644 index 00000000..f066bde4 --- /dev/null +++ b/onionshare/web/share_mode.py @@ -0,0 +1,60 @@ +import os +import tempfile +import zipfile + + +class ZipWriter(object): + """ + ZipWriter accepts files and directories and compresses them into a zip file + with. If a zip_filename is not passed in, it will use the default onionshare + filename. + """ + def __init__(self, common, zip_filename=None, processed_size_callback=None): + self.common = common + self.cancel_compression = False + + if zip_filename: + self.zip_filename = zip_filename + else: + self.zip_filename = '{0:s}/onionshare_{1:s}.zip'.format(tempfile.mkdtemp(), self.common.random_string(4, 6)) + + self.z = zipfile.ZipFile(self.zip_filename, 'w', allowZip64=True) + self.processed_size_callback = processed_size_callback + if self.processed_size_callback is None: + self.processed_size_callback = lambda _: None + self._size = 0 + self.processed_size_callback(self._size) + + def add_file(self, filename): + """ + Add a file to the zip archive. + """ + self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(filename) + self.processed_size_callback(self._size) + + def add_dir(self, filename): + """ + Add a directory, and all of its children, to the zip archive. + """ + dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/' + for dirpath, dirnames, filenames in os.walk(filename): + for f in filenames: + # Canceling early? + if self.cancel_compression: + return False + + full_filename = os.path.join(dirpath, f) + if not os.path.islink(full_filename): + arc_filename = full_filename[len(dir_to_strip):] + self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED) + self._size += os.path.getsize(full_filename) + self.processed_size_callback(self._size) + + return True + + def close(self): + """ + Close the zip archive. + """ + self.z.close() diff --git a/onionshare/web/web.py b/onionshare/web/web.py new file mode 100644 index 00000000..ff149f21 --- /dev/null +++ b/onionshare/web/web.py @@ -0,0 +1,647 @@ +import hmac +import logging +import mimetypes +import os +import queue +import socket +import sys +import tempfile +from distutils.version import LooseVersion as Version +from urllib.request import urlopen + +import flask +from flask import ( + Flask, Response, request, render_template, abort, make_response, + flash, redirect, __version__ as flask_version +) +from werkzeug.utils import secure_filename + +from .. import strings +from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable + +from .share_mode import ZipWriter +from .receive_mode import ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest + + +# Stub out flask's show_server_banner function, to avoiding showing warnings that +# are not applicable to OnionShare +def stubbed_show_server_banner(env, debug, app_import_path, eager_loading): + pass + +flask.cli.show_server_banner = stubbed_show_server_banner + + +class Web(object): + """ + The Web object is the OnionShare web server, powered by flask + """ + REQUEST_LOAD = 0 + REQUEST_STARTED = 1 + REQUEST_PROGRESS = 2 + REQUEST_OTHER = 3 + REQUEST_CANCELED = 4 + REQUEST_RATE_LIMIT = 5 + REQUEST_CLOSE_SERVER = 6 + REQUEST_UPLOAD_FILE_RENAMED = 7 + REQUEST_UPLOAD_FINISHED = 8 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 + + def __init__(self, common, gui_mode, receive_mode=False): + self.common = common + + # The flask app + self.app = Flask(__name__, + static_folder=self.common.get_resource_path('static'), + template_folder=self.common.get_resource_path('templates')) + self.app.secret_key = self.common.random_string(8) + + # Debug mode? + if self.common.debug: + self.debug_mode() + + # Are we running in GUI mode? + self.gui_mode = gui_mode + + # Are we using receive mode? + self.receive_mode = receive_mode + if self.receive_mode: + # Use custom WSGI middleware, to modify environ + self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) + # Use a custom Request class to track upload progess + self.app.request_class = ReceiveModeRequest + + # Starting in Flask 0.11, render_template_string autoescapes template variables + # by default. To prevent content injection through template variables in + # earlier versions of Flask, we force autoescaping in the Jinja2 template + # engine if we detect a Flask version with insecure default behavior. + if Version(flask_version) < Version('0.11'): + # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc + Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape + + # Information about the file + self.file_info = [] + self.is_zipped = False + self.download_filename = None + self.download_filesize = None + self.zip_writer = None + + self.security_headers = [ + ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), + ('X-Frame-Options', 'DENY'), + ('X-Xss-Protection', '1; mode=block'), + ('X-Content-Type-Options', 'nosniff'), + ('Referrer-Policy', 'no-referrer'), + ('Server', 'OnionShare') + ] + + self.q = queue.Queue() + + self.slug = None + + self.download_count = 0 + self.upload_count = 0 + + self.error404_count = 0 + + # If "Stop After First Download" is checked (stay_open == False), only allow + # one download at a time. + self.download_in_progress = False + + self.done = False + + # If the client closes the OnionShare window while a download is in progress, + # it should immediately stop serving the file. The client_cancel global is + # used to tell the download function that the client is canceling the download. + self.client_cancel = False + + # shutting down the server only works within the context of flask, so the easiest way to do it is over http + self.shutdown_slug = self.common.random_string(16) + + # Keep track if the server is running + self.running = False + + # Define the ewb app routes + self.common_routes() + if self.receive_mode: + self.receive_routes() + else: + self.send_routes() + + def send_routes(self): + """ + The web app routes for sharing files + """ + @self.app.route("/") + def index(slug_candidate): + self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return index_logic() + + def index_logic(slug_candidate=''): + """ + Render the template for the onionshare landing page. + """ + self.add_request(Web.REQUEST_LOAD, request.path) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) + + # If download is allowed to continue, serve download page + if self.slug: + r = make_response(render_template( + 'send.html', + slug=self.slug, + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) + else: + # If download is allowed to continue, serve download page + r = make_response(render_template( + 'send.html', + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) + return self.add_security_headers(r) + + @self.app.route("//download") + def download(slug_candidate): + self.check_slug_candidate(slug_candidate) + return download_logic() + + @self.app.route("/download") + def download_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return download_logic() + + def download_logic(slug_candidate=''): + """ + Download the zip file. + """ + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.stay_open and self.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.add_security_headers(r) + + # Each download has a unique id + download_id = self.download_count + self.download_count += 1 + + # Prepare some variables to use inside generate() function below + # which is outside of the request context + shutdown_func = request.environ.get('werkzeug.server.shutdown') + path = request.path + + # Tell GUI the download started + self.add_request(Web.REQUEST_STARTED, path, { + 'id': download_id} + ) + + dirname = os.path.dirname(self.download_filename) + basename = os.path.basename(self.download_filename) + + def generate(): + # The user hasn't canceled the download + self.client_cancel = False + + # Starting a new download + if not self.stay_open: + self.download_in_progress = True + + chunk_size = 102400 # 100kb + + fp = open(self.download_filename, 'rb') + self.done = False + canceled = False + while not self.done: + # The user has canceled the download, so stop serving the file + if self.client_cancel: + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) + break + + chunk = fp.read(chunk_size) + if chunk == b'': + self.done = True + else: + try: + yield chunk + + # tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 + + # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) + if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + self.add_request(Web.REQUEST_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) + self.done = False + except: + # looks like the download was canceled + self.done = True + canceled = True + + # tell the GUI the download has canceled + self.add_request(Web.REQUEST_CANCELED, path, { + 'id': download_id + }) + + fp.close() + + if self.common.platform != 'Darwin': + sys.stdout.write("\n") + + # Download is finished + if not self.stay_open: + self.download_in_progress = False + + # Close the server, if necessary + if not self.stay_open and not canceled: + print(strings._("closing_automatically")) + self.running = False + try: + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() + except: + pass + + r = Response(generate()) + r.headers.set('Content-Length', self.download_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + r = self.add_security_headers(r) + # guess content type + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r + + def receive_routes(self): + """ + The web app routes for receiving files + """ + def index_logic(): + self.add_request(Web.REQUEST_LOAD, request.path) + + if self.common.settings.get('public_mode'): + upload_action = '/upload' + close_action = '/close' + else: + upload_action = '/{}/upload'.format(self.slug) + close_action = '/{}/close'.format(self.slug) + + r = make_response(render_template( + 'receive.html', + upload_action=upload_action, + close_action=close_action, + receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) + return self.add_security_headers(r) + + @self.app.route("/") + def index(slug_candidate): + self.check_slug_candidate(slug_candidate) + return index_logic() + + @self.app.route("/") + def index_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return index_logic() + + + def upload_logic(slug_candidate=''): + """ + Upload files. + """ + # Make sure downloads_dir exists + valid = True + try: + self.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + + self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') + + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + @self.app.route("//upload", methods=['POST']) + def upload(slug_candidate): + self.check_slug_candidate(slug_candidate) + return upload_logic(slug_candidate) + + @self.app.route("/upload", methods=['POST']) + def upload_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return upload_logic() + + + def close_logic(slug_candidate=''): + if self.common.settings.get('receive_allow_receiver_shutdown'): + self.force_shutdown() + r = make_response(render_template('closed.html')) + self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) + return self.add_security_headers(r) + else: + return redirect('/{}'.format(slug_candidate)) + + @self.app.route("//close", methods=['POST']) + def close(slug_candidate): + self.check_slug_candidate(slug_candidate) + return close_logic(slug_candidate) + + @self.app.route("/close", methods=['POST']) + def close_public(): + if not self.common.settings.get('public_mode'): + return self.error404() + return close_logic() + + def common_routes(self): + """ + Common web app routes between sending and receiving + """ + @self.app.errorhandler(404) + def page_not_found(e): + """ + 404 error page. + """ + return self.error404() + + @self.app.route("//shutdown") + def shutdown(slug_candidate): + """ + Stop the flask web server, from the context of an http request. + """ + self.check_shutdown_slug_candidate(slug_candidate) + self.force_shutdown() + return "" + + def error404(self): + self.add_request(Web.REQUEST_OTHER, request.path) + if request.path != '/favicon.ico': + self.error404_count += 1 + + # In receive mode, with public mode enabled, skip rate limiting 404s + if not self.common.settings.get('public_mode'): + if self.error404_count == 20: + self.add_request(Web.REQUEST_RATE_LIMIT, request.path) + self.force_shutdown() + print(strings._('error_rate_limit')) + + r = make_response(render_template('404.html'), 404) + return self.add_security_headers(r) + + def add_security_headers(self, r): + """ + Add security headers to a request + """ + for header, value in self.security_headers: + r.headers.set(header, value) + return r + + def set_file_info(self, filenames, processed_size_callback=None): + """ + Using the list of filenames being shared, fill in details that the web + page will need to display. This includes zipping up the file in order to + get the zip file's name and size. + """ + self.common.log("Web", "set_file_info") + self.cancel_compression = False + + # build file info list + self.file_info = {'files': [], 'dirs': []} + for filename in filenames: + info = { + 'filename': filename, + 'basename': os.path.basename(filename.rstrip('/')) + } + if os.path.isfile(filename): + info['size'] = os.path.getsize(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['files'].append(info) + if os.path.isdir(filename): + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) + self.file_info['dirs'].append(info) + self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) + self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) + + # Check if there's only 1 file and no folders + if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: + self.is_zipped = False + self.download_filename = self.file_info['files'][0]['filename'] + self.download_filesize = self.file_info['files'][0]['size'] + else: + # Zip up the files and folders + self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) + self.download_filename = self.zip_writer.zip_filename + for info in self.file_info['files']: + self.zip_writer.add_file(info['filename']) + # Canceling early? + if self.cancel_compression: + self.zip_writer.close() + return False + + for info in self.file_info['dirs']: + if not self.zip_writer.add_dir(info['filename']): + return False + + self.zip_writer.close() + self.download_filesize = os.path.getsize(self.download_filename) + self.is_zipped = True + + return True + + def _safe_select_jinja_autoescape(self, filename): + if filename is None: + return True + return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) + + def add_request(self, request_type, path, data=None): + """ + Add a request to the queue, to communicate with the GUI. + """ + self.q.put({ + 'type': request_type, + 'path': path, + 'data': data + }) + + def generate_slug(self, persistent_slug=None): + self.common.log('Web', 'generate_slug', 'persistent_slug={}'.format(persistent_slug)) + if persistent_slug != None and persistent_slug != '': + self.slug = persistent_slug + self.common.log('Web', 'generate_slug', 'persistent_slug sent, so slug is: "{}"'.format(self.slug)) + else: + self.slug = self.common.build_slug() + self.common.log('Web', 'generate_slug', 'built random slug: "{}"'.format(self.slug)) + + def debug_mode(self): + """ + Turn on debugging mode, which will log flask errors to a debug file. + """ + temp_dir = tempfile.gettempdir() + log_handler = logging.FileHandler( + os.path.join(temp_dir, 'onionshare_server.log')) + log_handler.setLevel(logging.WARNING) + self.app.logger.addHandler(log_handler) + + def check_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_slug_candidate: slug_candidate={}'.format(slug_candidate)) + if self.common.settings.get('public_mode'): + abort(404) + if not hmac.compare_digest(self.slug, slug_candidate): + abort(404) + + def check_shutdown_slug_candidate(self, slug_candidate): + self.common.log('Web', 'check_shutdown_slug_candidate: slug_candidate={}'.format(slug_candidate)) + if not hmac.compare_digest(self.shutdown_slug, slug_candidate): + abort(404) + + def force_shutdown(self): + """ + Stop the flask web server, from the context of the flask app. + """ + # Shutdown the flask service + try: + func = request.environ.get('werkzeug.server.shutdown') + if func is None: + raise RuntimeError('Not running with the Werkzeug Server') + func() + except: + pass + self.running = False + + def start(self, port, stay_open=False, public_mode=False, persistent_slug=None): + """ + Start the flask web server. + """ + self.common.log('Web', 'start', 'port={}, stay_open={}, public_mode={}, persistent_slug={}'.format(port, stay_open, public_mode, persistent_slug)) + if not public_mode: + self.generate_slug(persistent_slug) + + self.stay_open = stay_open + + # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) + if os.path.exists('/usr/share/anon-ws-base-files/workstation'): + host = '0.0.0.0' + else: + host = '127.0.0.1' + + self.running = True + self.app.run(host=host, port=port, threaded=True) + + def stop(self, port): + """ + Stop the flask web server by loading /shutdown. + """ + + # If the user cancels the download, let the download function know to stop + # serving the file + self.client_cancel = True + + # To stop flask, load http://127.0.0.1://shutdown + if self.running: + try: + s = socket.socket() + s.connect(('127.0.0.1', port)) + s.sendall('GET /{0:s}/shutdown HTTP/1.1\r\n\r\n'.format(self.shutdown_slug)) + except: + try: + urlopen('http://127.0.0.1:{0:d}/{1:s}/shutdown'.format(port, self.shutdown_slug)).read() + except: + pass -- cgit v1.2.3-54-g00ecf From 357985fd124938d0d6e8af8d6fb19fed13cfe2b1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:45:13 -0700 Subject: Fix tests to point to new location of ZipWriter class --- test/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 610a43ea..8ac7efb8 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -64,7 +64,7 @@ def temp_file_1024_delete(): # pytest > 2.9 only needs @pytest.fixture @pytest.yield_fixture(scope='session') def custom_zw(): - zw = web.ZipWriter( + zw = web.share_mode.ZipWriter( common.Common(), zip_filename=common.Common.random_string(4, 6), processed_size_callback=lambda _: 'custom_callback' @@ -77,7 +77,7 @@ def custom_zw(): # pytest > 2.9 only needs @pytest.fixture @pytest.yield_fixture(scope='session') def default_zw(): - zw = web.ZipWriter(common.Common()) + zw = web.share_mode.ZipWriter(common.Common()) yield zw zw.close() tmp_dir = os.path.dirname(zw.zip_filename) -- cgit v1.2.3-54-g00ecf From 8ce90fdd60e6d3d01445f9b72e2ad4a1190ce419 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Sep 2018 23:58:27 -0700 Subject: Refactor web to push share and receive mode logic into their respective files --- onionshare/web/receive_mode.py | 157 ++++++++++++++++++- onionshare/web/share_mode.py | 177 +++++++++++++++++++++ onionshare/web/web.py | 338 +---------------------------------------- 3 files changed, 338 insertions(+), 334 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 90accc8c..0ebc9ccd 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -1,10 +1,165 @@ +import os import tempfile from datetime import datetime -from flask import Request +from flask import Request, request, render_template, make_response, flash, redirect +from werkzeug.utils import secure_filename +from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable from .. import strings +def receive_routes(web): + """ + The web app routes for receiving files + """ + def index_logic(): + web.add_request(web.REQUEST_LOAD, request.path) + + if web.common.settings.get('public_mode'): + upload_action = '/upload' + close_action = '/close' + else: + upload_action = '/{}/upload'.format(web.slug) + close_action = '/{}/close'.format(web.slug) + + r = make_response(render_template( + 'receive.html', + upload_action=upload_action, + close_action=close_action, + receive_allow_receiver_shutdown=web.common.settings.get('receive_allow_receiver_shutdown'))) + return web.add_security_headers(r) + + @web.app.route("/") + def index(slug_candidate): + web.check_slug_candidate(slug_candidate) + return index_logic() + + @web.app.route("/") + def index_public(): + if not web.common.settings.get('public_mode'): + return web.error404() + return index_logic() + + + def upload_logic(slug_candidate=''): + """ + Upload files. + """ + # Make sure downloads_dir exists + valid = True + try: + web.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + web.add_request(web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(web.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + web.add_request(web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(web.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if web.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(web.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(web.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(web.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + web.add_request(web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + + web.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') + + if web.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + @web.app.route("//upload", methods=['POST']) + def upload(slug_candidate): + web.check_slug_candidate(slug_candidate) + return upload_logic(slug_candidate) + + @web.app.route("/upload", methods=['POST']) + def upload_public(): + if not web.common.settings.get('public_mode'): + return web.error404() + return upload_logic() + + + def close_logic(slug_candidate=''): + if web.common.settings.get('receive_allow_receiver_shutdown'): + web.force_shutdown() + r = make_response(render_template('closed.html')) + web.add_request(web.REQUEST_CLOSE_SERVER, request.path) + return web.add_security_headers(r) + else: + return redirect('/{}'.format(slug_candidate)) + + @web.app.route("//close", methods=['POST']) + def close(slug_candidate): + web.check_slug_candidate(slug_candidate) + return close_logic(slug_candidate) + + @web.app.route("/close", methods=['POST']) + def close_public(): + if not web.common.settings.get('public_mode'): + return web.error404() + return close_logic() + + class ReceiveModeWSGIMiddleware(object): """ Custom WSGI middleware in order to attach the Web object to environ, so diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index f066bde4..58cc9b99 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -1,6 +1,183 @@ import os +import sys import tempfile import zipfile +import mimetypes +from flask import Response, request, render_template, make_response + +from .. import strings + + +def share_routes(web): + """ + The web app routes for sharing files + """ + @web.app.route("/") + def index(slug_candidate): + web.check_slug_candidate(slug_candidate) + return index_logic() + + @web.app.route("/") + def index_public(): + if not web.common.settings.get('public_mode'): + return web.error404() + return index_logic() + + def index_logic(slug_candidate=''): + """ + Render the template for the onionshare landing page. + """ + web.add_request(web.REQUEST_LOAD, request.path) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not web.stay_open and web.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return web.add_security_headers(r) + + # If download is allowed to continue, serve download page + if web.slug: + r = make_response(render_template( + 'send.html', + slug=web.slug, + file_info=web.file_info, + filename=os.path.basename(web.download_filename), + filesize=web.download_filesize, + filesize_human=web.common.human_readable_filesize(web.download_filesize), + is_zipped=web.is_zipped)) + else: + # If download is allowed to continue, serve download page + r = make_response(render_template( + 'send.html', + file_info=web.file_info, + filename=os.path.basename(web.download_filename), + filesize=web.download_filesize, + filesize_human=web.common.human_readable_filesize(web.download_filesize), + is_zipped=web.is_zipped)) + return web.add_security_headers(r) + + @web.app.route("//download") + def download(slug_candidate): + web.check_slug_candidate(slug_candidate) + return download_logic() + + @web.app.route("/download") + def download_public(): + if not web.common.settings.get('public_mode'): + return web.error404() + return download_logic() + + def download_logic(slug_candidate=''): + """ + Download the zip file. + """ + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not web.stay_open and web.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return web.add_security_headers(r) + + # Each download has a unique id + download_id = web.download_count + web.download_count += 1 + + # Prepare some variables to use inside generate() function below + # which is outside of the request context + shutdown_func = request.environ.get('werkzeug.server.shutdown') + path = request.path + + # Tell GUI the download started + web.add_request(web.REQUEST_STARTED, path, { + 'id': download_id} + ) + + dirname = os.path.dirname(web.download_filename) + basename = os.path.basename(web.download_filename) + + def generate(): + # The user hasn't canceled the download + web.client_cancel = False + + # Starting a new download + if not web.stay_open: + web.download_in_progress = True + + chunk_size = 102400 # 100kb + + fp = open(web.download_filename, 'rb') + web.done = False + canceled = False + while not web.done: + # The user has canceled the download, so stop serving the file + if web.client_cancel: + web.add_request(web.REQUEST_CANCELED, path, { + 'id': download_id + }) + break + + chunk = fp.read(chunk_size) + if chunk == b'': + web.done = True + else: + try: + yield chunk + + # tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / web.download_filesize) * 100 + + # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) + if not web.gui_mode or web.common.platform == 'Linux' or web.common.platform == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(web.common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + web.add_request(web.REQUEST_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) + web.done = False + except: + # looks like the download was canceled + web.done = True + canceled = True + + # tell the GUI the download has canceled + web.add_request(web.REQUEST_CANCELED, path, { + 'id': download_id + }) + + fp.close() + + if web.common.platform != 'Darwin': + sys.stdout.write("\n") + + # Download is finished + if not web.stay_open: + web.download_in_progress = False + + # Close the server, if necessary + if not web.stay_open and not canceled: + print(strings._("closing_automatically")) + web.running = False + try: + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() + except: + pass + + r = Response(generate()) + r.headers.set('Content-Length', web.download_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + r = web.add_security_headers(r) + # guess content type + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r class ZipWriter(object): diff --git a/onionshare/web/web.py b/onionshare/web/web.py index ff149f21..0a6e6964 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -1,6 +1,5 @@ import hmac import logging -import mimetypes import os import queue import socket @@ -10,17 +9,12 @@ from distutils.version import LooseVersion as Version from urllib.request import urlopen import flask -from flask import ( - Flask, Response, request, render_template, abort, make_response, - flash, redirect, __version__ as flask_version -) -from werkzeug.utils import secure_filename +from flask import Flask, request, render_template, abort, make_response, __version__ as flask_version from .. import strings -from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable -from .share_mode import ZipWriter -from .receive_mode import ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest +from .share_mode import share_routes, ZipWriter +from .receive_mode import receive_routes, ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest # Stub out flask's show_server_banner function, to avoiding showing warnings that @@ -124,331 +118,9 @@ class Web(object): # Define the ewb app routes self.common_routes() if self.receive_mode: - self.receive_routes() + receive_routes(self) else: - self.send_routes() - - def send_routes(self): - """ - The web app routes for sharing files - """ - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - def index_logic(slug_candidate=''): - """ - Render the template for the onionshare landing page. - """ - self.add_request(Web.REQUEST_LOAD, request.path) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # If download is allowed to continue, serve download page - if self.slug: - r = make_response(render_template( - 'send.html', - slug=self.slug, - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - else: - # If download is allowed to continue, serve download page - r = make_response(render_template( - 'send.html', - file_info=self.file_info, - filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, - filesize_human=self.common.human_readable_filesize(self.download_filesize), - is_zipped=self.is_zipped)) - return self.add_security_headers(r) - - @self.app.route("//download") - def download(slug_candidate): - self.check_slug_candidate(slug_candidate) - return download_logic() - - @self.app.route("/download") - def download_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return download_logic() - - def download_logic(slug_candidate=''): - """ - Download the zip file. - """ - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not self.stay_open and self.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return self.add_security_headers(r) - - # Each download has a unique id - download_id = self.download_count - self.download_count += 1 - - # Prepare some variables to use inside generate() function below - # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') - path = request.path - - # Tell GUI the download started - self.add_request(Web.REQUEST_STARTED, path, { - 'id': download_id} - ) - - dirname = os.path.dirname(self.download_filename) - basename = os.path.basename(self.download_filename) - - def generate(): - # The user hasn't canceled the download - self.client_cancel = False - - # Starting a new download - if not self.stay_open: - self.download_in_progress = True - - chunk_size = 102400 # 100kb - - fp = open(self.download_filename, 'rb') - self.done = False - canceled = False - while not self.done: - # The user has canceled the download, so stop serving the file - if self.client_cancel: - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - break - - chunk = fp.read(chunk_size) - if chunk == b'': - self.done = True - else: - try: - yield chunk - - # tell GUI the progress - downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 - - # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.gui_mode or self.common.platform == 'Linux' or self.common.platform == 'BSD': - sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) - sys.stdout.flush() - - self.add_request(Web.REQUEST_PROGRESS, path, { - 'id': download_id, - 'bytes': downloaded_bytes - }) - self.done = False - except: - # looks like the download was canceled - self.done = True - canceled = True - - # tell the GUI the download has canceled - self.add_request(Web.REQUEST_CANCELED, path, { - 'id': download_id - }) - - fp.close() - - if self.common.platform != 'Darwin': - sys.stdout.write("\n") - - # Download is finished - if not self.stay_open: - self.download_in_progress = False - - # Close the server, if necessary - if not self.stay_open and not canceled: - print(strings._("closing_automatically")) - self.running = False - try: - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() - except: - pass - - r = Response(generate()) - r.headers.set('Content-Length', self.download_filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) - r = self.add_security_headers(r) - # guess content type - (content_type, _) = mimetypes.guess_type(basename, strict=False) - if content_type is not None: - r.headers.set('Content-Type', content_type) - return r - - def receive_routes(self): - """ - The web app routes for receiving files - """ - def index_logic(): - self.add_request(Web.REQUEST_LOAD, request.path) - - if self.common.settings.get('public_mode'): - upload_action = '/upload' - close_action = '/close' - else: - upload_action = '/{}/upload'.format(self.slug) - close_action = '/{}/close'.format(self.slug) - - r = make_response(render_template( - 'receive.html', - upload_action=upload_action, - close_action=close_action, - receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) - return self.add_security_headers(r) - - @self.app.route("/") - def index(slug_candidate): - self.check_slug_candidate(slug_candidate) - return index_logic() - - @self.app.route("/") - def index_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return index_logic() - - - def upload_logic(slug_candidate=''): - """ - Upload files. - """ - # Make sure downloads_dir exists - valid = True - try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.add_request(Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.add_request(Web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) - - self.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//upload", methods=['POST']) - def upload(slug_candidate): - self.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) - - @self.app.route("/upload", methods=['POST']) - def upload_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return upload_logic() - - - def close_logic(slug_candidate=''): - if self.common.settings.get('receive_allow_receiver_shutdown'): - self.force_shutdown() - r = make_response(render_template('closed.html')) - self.add_request(Web.REQUEST_CLOSE_SERVER, request.path) - return self.add_security_headers(r) - else: - return redirect('/{}'.format(slug_candidate)) - - @self.app.route("//close", methods=['POST']) - def close(slug_candidate): - self.check_slug_candidate(slug_candidate) - return close_logic(slug_candidate) - - @self.app.route("/close", methods=['POST']) - def close_public(): - if not self.common.settings.get('public_mode'): - return self.error404() - return close_logic() + share_routes(self) def common_routes(self): """ -- cgit v1.2.3-54-g00ecf From cc9f646f8b2021dc279d045146bcd42ca9d6dc39 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 11:14:32 -0700 Subject: Refactor web even more to all of the share and receive web logic into ShareModeWeb and ReceiveModeWeb classes --- onionshare/__init__.py | 2 +- onionshare/web/receive_mode.py | 292 ++++++++++++------------ onionshare/web/share_mode.py | 379 ++++++++++++++++++-------------- onionshare/web/web.py | 84 ++----- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/share_mode/__init__.py | 2 +- onionshare_gui/share_mode/threads.py | 2 +- test/test_onionshare_web.py | 32 +-- 8 files changed, 408 insertions(+), 387 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 2f57ccf2..9c390fa8 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -119,7 +119,7 @@ def main(cwd=None): # Prepare files to share print(strings._("preparing_files")) try: - web.set_file_info(filenames) + web.share_mode.set_file_info(filenames) if web.is_zipped: app.cleanup_filenames.append(web.download_filename) except OSError as e: diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 0ebc9ccd..ab5f5f13 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -8,156 +8,164 @@ from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable from .. import strings -def receive_routes(web): +class ReceiveModeWeb(object): """ - The web app routes for receiving files + All of the web logic for receive mode """ - def index_logic(): - web.add_request(web.REQUEST_LOAD, request.path) - - if web.common.settings.get('public_mode'): - upload_action = '/upload' - close_action = '/close' - else: - upload_action = '/{}/upload'.format(web.slug) - close_action = '/{}/close'.format(web.slug) - - r = make_response(render_template( - 'receive.html', - upload_action=upload_action, - close_action=close_action, - receive_allow_receiver_shutdown=web.common.settings.get('receive_allow_receiver_shutdown'))) - return web.add_security_headers(r) - - @web.app.route("/") - def index(slug_candidate): - web.check_slug_candidate(slug_candidate) - return index_logic() - - @web.app.route("/") - def index_public(): - if not web.common.settings.get('public_mode'): - return web.error404() - return index_logic() - - - def upload_logic(slug_candidate=''): + def __init__(self, web): + self.web = web + self.define_routes() + + def define_routes(self): """ - Upload files. + The web app routes for receiving files """ - # Make sure downloads_dir exists - valid = True - try: - web.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - web.add_request(web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(web.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - web.add_request(web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(web.common.settings.get('downloads_dir'))) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if web.common.settings.get('public_mode'): + def index_logic(): + self.web.add_request(self.web.REQUEST_LOAD, request.path) + + if self.web.common.settings.get('public_mode'): + upload_action = '/upload' + close_action = '/close' + else: + upload_action = '/{}/upload'.format(self.web.slug) + close_action = '/{}/close'.format(self.web.slug) + + r = make_response(render_template( + 'receive.html', + upload_action=upload_action, + close_action=close_action, + receive_allow_receiver_shutdown=self.web.common.settings.get('receive_allow_receiver_shutdown'))) + return self.web.add_security_headers(r) + + @self.web.app.route("/") + def index(slug_candidate): + self.web.check_slug_candidate(slug_candidate) + return index_logic() + + @self.web.app.route("/") + def index_public(): + if not self.web.common.settings.get('public_mode'): + return self.web.error404() + return index_logic() + + + def upload_logic(slug_candidate=''): + """ + Upload files. + """ + # Make sure downloads_dir exists + valid = True + try: + self.web.common.validate_downloads_dir() + except DownloadsDirErrorCannotCreate: + self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) + print(strings._('error_cannot_create_downloads_dir').format(self.web.common.settings.get('downloads_dir'))) + valid = False + except DownloadsDirErrorNotWritable: + self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) + print(strings._('error_downloads_dir_not_writable').format(self.web.common.settings.get('downloads_dir'))) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if self.web.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + files = request.files.getlist('file[]') + filenames = [] + print('') + for f in files: + if f.filename != '': + # Automatically rename the file, if a file of the same name already exists + filename = secure_filename(f.filename) + filenames.append(filename) + local_path = os.path.join(self.web.common.settings.get('downloads_dir'), filename) + if os.path.exists(local_path): + if '.' in filename: + # Add "-i", e.g. change "foo.txt" to "foo-2.txt" + parts = filename.split('.') + name = parts[:-1] + ext = parts[-1] + + i = 2 + valid = False + while not valid: + new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) + local_path = os.path.join(self.web.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + else: + # If no extension, just add "-i", e.g. change "foo" to "foo-2" + i = 2 + valid = False + while not valid: + new_filename = '{}-{}'.format(filename, i) + local_path = os.path.join(self.web.common.settings.get('downloads_dir'), new_filename) + if os.path.exists(local_path): + i += 1 + else: + valid = True + + basename = os.path.basename(local_path) + if f.filename != basename: + # Tell the GUI that the file has changed names + self.web.add_request(self.web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { + 'id': request.upload_id, + 'old_filename': f.filename, + 'new_filename': basename + }) + + self.web.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + print(strings._('receive_mode_received_file').format(local_path)) + f.save(local_path) + + # Note that flash strings are on English, and not translated, on purpose, + # to avoid leaking the locale of the OnionShare user + if len(filenames) == 0: + flash('No files uploaded', 'info') + else: + for filename in filenames: + flash('Sent {}'.format(filename), 'info') + + if self.web.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) - files = request.files.getlist('file[]') - filenames = [] - print('') - for f in files: - if f.filename != '': - # Automatically rename the file, if a file of the same name already exists - filename = secure_filename(f.filename) - filenames.append(filename) - local_path = os.path.join(web.common.settings.get('downloads_dir'), filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(web.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(web.common.settings.get('downloads_dir'), new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - - basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - web.add_request(web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) - - web.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - - # Note that flash strings are on English, and not translated, on purpose, - # to avoid leaking the locale of the OnionShare user - if len(filenames) == 0: - flash('No files uploaded', 'info') - else: - for filename in filenames: - flash('Sent {}'.format(filename), 'info') - - if web.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - - @web.app.route("//upload", methods=['POST']) - def upload(slug_candidate): - web.check_slug_candidate(slug_candidate) - return upload_logic(slug_candidate) - - @web.app.route("/upload", methods=['POST']) - def upload_public(): - if not web.common.settings.get('public_mode'): - return web.error404() - return upload_logic() - - - def close_logic(slug_candidate=''): - if web.common.settings.get('receive_allow_receiver_shutdown'): - web.force_shutdown() - r = make_response(render_template('closed.html')) - web.add_request(web.REQUEST_CLOSE_SERVER, request.path) - return web.add_security_headers(r) - else: - return redirect('/{}'.format(slug_candidate)) - - @web.app.route("//close", methods=['POST']) - def close(slug_candidate): - web.check_slug_candidate(slug_candidate) - return close_logic(slug_candidate) - - @web.app.route("/close", methods=['POST']) - def close_public(): - if not web.common.settings.get('public_mode'): - return web.error404() - return close_logic() + @self.web.app.route("//upload", methods=['POST']) + def upload(slug_candidate): + self.web.check_slug_candidate(slug_candidate) + return upload_logic(slug_candidate) + + @self.web.app.route("/upload", methods=['POST']) + def upload_public(): + if not self.web.common.settings.get('public_mode'): + return self.web.error404() + return upload_logic() + + + def close_logic(slug_candidate=''): + if self.web.common.settings.get('receive_allow_receiver_shutdown'): + self.web.force_shutdown() + r = make_response(render_template('closed.html')) + self.web.add_request(self.web.REQUEST_CLOSE_SERVER, request.path) + return self.web.add_security_headers(r) + else: + return redirect('/{}'.format(slug_candidate)) + + @self.web.app.route("//close", methods=['POST']) + def close(slug_candidate): + self.web.check_slug_candidate(slug_candidate) + return close_logic(slug_candidate) + + @self.web.app.route("/close", methods=['POST']) + def close_public(): + if not self.web.common.settings.get('public_mode'): + return self.web.error404() + return close_logic() class ReceiveModeWSGIMiddleware(object): diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 58cc9b99..c8a411bb 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -8,176 +8,237 @@ from flask import Response, request, render_template, make_response from .. import strings -def share_routes(web): +class ShareModeWeb(object): """ - The web app routes for sharing files + All of the web logic for share mode """ - @web.app.route("/") - def index(slug_candidate): - web.check_slug_candidate(slug_candidate) - return index_logic() - - @web.app.route("/") - def index_public(): - if not web.common.settings.get('public_mode'): - return web.error404() - return index_logic() - - def index_logic(slug_candidate=''): + def __init__(self, web): + self.web = web + self.define_routes() + + def define_routes(self): """ - Render the template for the onionshare landing page. + The web app routes for sharing files """ - web.add_request(web.REQUEST_LOAD, request.path) - - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not web.stay_open and web.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return web.add_security_headers(r) - - # If download is allowed to continue, serve download page - if web.slug: - r = make_response(render_template( - 'send.html', - slug=web.slug, - file_info=web.file_info, - filename=os.path.basename(web.download_filename), - filesize=web.download_filesize, - filesize_human=web.common.human_readable_filesize(web.download_filesize), - is_zipped=web.is_zipped)) - else: + @self.web.app.route("/") + def index(slug_candidate): + self.web.check_slug_candidate(slug_candidate) + return index_logic() + + @self.web.app.route("/") + def index_public(): + if not self.web.common.settings.get('public_mode'): + return self.web.error404() + return index_logic() + + def index_logic(slug_candidate=''): + """ + Render the template for the onionshare landing page. + """ + self.web.add_request(self.web.REQUEST_LOAD, request.path) + + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.web.stay_open and self.web.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.web.add_security_headers(r) + # If download is allowed to continue, serve download page - r = make_response(render_template( - 'send.html', - file_info=web.file_info, - filename=os.path.basename(web.download_filename), - filesize=web.download_filesize, - filesize_human=web.common.human_readable_filesize(web.download_filesize), - is_zipped=web.is_zipped)) - return web.add_security_headers(r) - - @web.app.route("//download") - def download(slug_candidate): - web.check_slug_candidate(slug_candidate) - return download_logic() - - @web.app.route("/download") - def download_public(): - if not web.common.settings.get('public_mode'): - return web.error404() - return download_logic() - - def download_logic(slug_candidate=''): - """ - Download the zip file. - """ - # Deny new downloads if "Stop After First Download" is checked and there is - # currently a download - deny_download = not web.stay_open and web.download_in_progress - if deny_download: - r = make_response(render_template('denied.html')) - return web.add_security_headers(r) - - # Each download has a unique id - download_id = web.download_count - web.download_count += 1 - - # Prepare some variables to use inside generate() function below - # which is outside of the request context - shutdown_func = request.environ.get('werkzeug.server.shutdown') - path = request.path - - # Tell GUI the download started - web.add_request(web.REQUEST_STARTED, path, { - 'id': download_id} - ) - - dirname = os.path.dirname(web.download_filename) - basename = os.path.basename(web.download_filename) - - def generate(): - # The user hasn't canceled the download - web.client_cancel = False - - # Starting a new download - if not web.stay_open: - web.download_in_progress = True - - chunk_size = 102400 # 100kb - - fp = open(web.download_filename, 'rb') - web.done = False - canceled = False - while not web.done: - # The user has canceled the download, so stop serving the file - if web.client_cancel: - web.add_request(web.REQUEST_CANCELED, path, { - 'id': download_id - }) - break - - chunk = fp.read(chunk_size) - if chunk == b'': - web.done = True - else: - try: - yield chunk + if self.web.slug: + r = make_response(render_template( + 'send.html', + slug=self.web.slug, + file_info=self.web.file_info, + filename=os.path.basename(self.web.download_filename), + filesize=self.web.download_filesize, + filesize_human=self.web.common.human_readable_filesize(self.web.download_filesize), + is_zipped=self.web.is_zipped)) + else: + # If download is allowed to continue, serve download page + r = make_response(render_template( + 'send.html', + file_info=self.web.file_info, + filename=os.path.basename(self.web.download_filename), + filesize=self.web.download_filesize, + filesize_human=self.web.common.human_readable_filesize(self.web.download_filesize), + is_zipped=self.web.is_zipped)) + return self.web.add_security_headers(r) + + @self.web.app.route("//download") + def download(slug_candidate): + self.web.check_slug_candidate(slug_candidate) + return download_logic() + + @self.web.app.route("/download") + def download_public(): + if not self.web.common.settings.get('public_mode'): + return self.web.error404() + return download_logic() + + def download_logic(slug_candidate=''): + """ + Download the zip file. + """ + # Deny new downloads if "Stop After First Download" is checked and there is + # currently a download + deny_download = not self.web.stay_open and self.web.download_in_progress + if deny_download: + r = make_response(render_template('denied.html')) + return self.web.add_security_headers(r) + + # Each download has a unique id + download_id = self.web.download_count + self.web.download_count += 1 + + # Prepare some variables to use inside generate() function below + # which is outside of the request context + shutdown_func = request.environ.get('werkzeug.server.shutdown') + path = request.path + + # Tell GUI the download started + self.web.add_request(self.web.REQUEST_STARTED, path, { + 'id': download_id} + ) + + dirname = os.path.dirname(self.web.download_filename) + basename = os.path.basename(self.web.download_filename) + + def generate(): + # The user hasn't canceled the download + self.web.client_cancel = False + + # Starting a new download + if not self.web.stay_open: + self.web.download_in_progress = True + + chunk_size = 102400 # 100kb + + fp = open(self.web.download_filename, 'rb') + self.web.done = False + canceled = False + while not self.web.done: + # The user has canceled the download, so stop serving the file + if self.web.client_cancel: + self.web.add_request(self.web.REQUEST_CANCELED, path, { + 'id': download_id + }) + break + + chunk = fp.read(chunk_size) + if chunk == b'': + self.web.done = True + else: + try: + yield chunk + + # tell GUI the progress + downloaded_bytes = fp.tell() + percent = (1.0 * downloaded_bytes / self.web.download_filesize) * 100 + + # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) + if not self.web.is_gui or self.web.common.platform == 'Linux' or self.web.common.platform == 'BSD': + sys.stdout.write( + "\r{0:s}, {1:.2f}% ".format(self.web.common.human_readable_filesize(downloaded_bytes), percent)) + sys.stdout.flush() + + self.web.add_request(self.web.REQUEST_PROGRESS, path, { + 'id': download_id, + 'bytes': downloaded_bytes + }) + self.web.done = False + except: + # looks like the download was canceled + self.web.done = True + canceled = True + + # tell the GUI the download has canceled + self.web.add_request(self.web.REQUEST_CANCELED, path, { + 'id': download_id + }) - # tell GUI the progress - downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / web.download_filesize) * 100 + fp.close() - # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not web.gui_mode or web.common.platform == 'Linux' or web.common.platform == 'BSD': - sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(web.common.human_readable_filesize(downloaded_bytes), percent)) - sys.stdout.flush() + if self.web.common.platform != 'Darwin': + sys.stdout.write("\n") - web.add_request(web.REQUEST_PROGRESS, path, { - 'id': download_id, - 'bytes': downloaded_bytes - }) - web.done = False + # Download is finished + if not self.web.stay_open: + self.web.download_in_progress = False + + # Close the server, if necessary + if not self.web.stay_open and not canceled: + print(strings._("closing_automatically")) + self.web.running = False + try: + if shutdown_func is None: + raise RuntimeError('Not running with the Werkzeug Server') + shutdown_func() except: - # looks like the download was canceled - web.done = True - canceled = True + pass + + r = Response(generate()) + r.headers.set('Content-Length', self.web.download_filesize) + r.headers.set('Content-Disposition', 'attachment', filename=basename) + r = self.web.add_security_headers(r) + # guess content type + (content_type, _) = mimetypes.guess_type(basename, strict=False) + if content_type is not None: + r.headers.set('Content-Type', content_type) + return r + + def set_file_info(self, filenames, processed_size_callback=None): + """ + Using the list of filenames being shared, fill in details that the web + page will need to display. This includes zipping up the file in order to + get the zip file's name and size. + """ + self.web.common.log("Web", "set_file_info") + self.web.cancel_compression = False + + # build file info list + self.web.file_info = {'files': [], 'dirs': []} + for filename in filenames: + info = { + 'filename': filename, + 'basename': os.path.basename(filename.rstrip('/')) + } + if os.path.isfile(filename): + info['size'] = os.path.getsize(filename) + info['size_human'] = self.web.common.human_readable_filesize(info['size']) + self.web.file_info['files'].append(info) + if os.path.isdir(filename): + info['size'] = self.web.common.dir_size(filename) + info['size_human'] = self.web.common.human_readable_filesize(info['size']) + self.web.file_info['dirs'].append(info) + self.web.file_info['files'] = sorted(self.web.file_info['files'], key=lambda k: k['basename']) + self.web.file_info['dirs'] = sorted(self.web.file_info['dirs'], key=lambda k: k['basename']) + + # Check if there's only 1 file and no folders + if len(self.web.file_info['files']) == 1 and len(self.web.file_info['dirs']) == 0: + self.web.is_zipped = False + self.web.download_filename = self.web.file_info['files'][0]['filename'] + self.web.download_filesize = self.web.file_info['files'][0]['size'] + else: + # Zip up the files and folders + self.web.zip_writer = ZipWriter(self.web.common, processed_size_callback=processed_size_callback) + self.web.download_filename = self.web.zip_writer.zip_filename + for info in self.web.file_info['files']: + self.web.zip_writer.add_file(info['filename']) + # Canceling early? + if self.web.cancel_compression: + self.web.zip_writer.close() + return False - # tell the GUI the download has canceled - web.add_request(web.REQUEST_CANCELED, path, { - 'id': download_id - }) + for info in self.web.file_info['dirs']: + if not self.web.zip_writer.add_dir(info['filename']): + return False - fp.close() - - if web.common.platform != 'Darwin': - sys.stdout.write("\n") - - # Download is finished - if not web.stay_open: - web.download_in_progress = False - - # Close the server, if necessary - if not web.stay_open and not canceled: - print(strings._("closing_automatically")) - web.running = False - try: - if shutdown_func is None: - raise RuntimeError('Not running with the Werkzeug Server') - shutdown_func() - except: - pass - - r = Response(generate()) - r.headers.set('Content-Length', web.download_filesize) - r.headers.set('Content-Disposition', 'attachment', filename=basename) - r = web.add_security_headers(r) - # guess content type - (content_type, _) = mimetypes.guess_type(basename, strict=False) - if content_type is not None: - r.headers.set('Content-Type', content_type) - return r + self.web.zip_writer.close() + self.web.download_filesize = os.path.getsize(self.web.download_filename) + self.web.is_zipped = True + + return True class ZipWriter(object): diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 0a6e6964..7959ae0f 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -13,8 +13,8 @@ from flask import Flask, request, render_template, abort, make_response, __versi from .. import strings -from .share_mode import share_routes, ZipWriter -from .receive_mode import receive_routes, ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest +from .share_mode import ShareModeWeb +from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest # Stub out flask's show_server_banner function, to avoiding showing warnings that @@ -41,7 +41,7 @@ class Web(object): REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 - def __init__(self, common, gui_mode, receive_mode=False): + def __init__(self, common, is_gui, mode='share'): self.common = common # The flask app @@ -55,11 +55,11 @@ class Web(object): self.debug_mode() # Are we running in GUI mode? - self.gui_mode = gui_mode + self.is_gui = is_gui # Are we using receive mode? - self.receive_mode = receive_mode - if self.receive_mode: + self.mode = mode + if self.mode == 'receive': # Use custom WSGI middleware, to modify environ self.app.wsgi_app = ReceiveModeWSGIMiddleware(self.app.wsgi_app, self) # Use a custom Request class to track upload progess @@ -115,14 +115,19 @@ class Web(object): # Keep track if the server is running self.running = False - # Define the ewb app routes - self.common_routes() - if self.receive_mode: - receive_routes(self) - else: - share_routes(self) + # Define the web app routes + self.define_common_routes() + + # Create the mode web object, which defines its own routes + self.share_mode = None + self.receive_mode = None + if self.mode == 'receive': + self.receive_mode = ReceiveModeWeb(self) + elif self.mode == 'share': + self.share_mode = ShareModeWeb(self) - def common_routes(self): + + def define_common_routes(self): """ Common web app routes between sending and receiving """ @@ -165,59 +170,6 @@ class Web(object): r.headers.set(header, value) return r - def set_file_info(self, filenames, processed_size_callback=None): - """ - Using the list of filenames being shared, fill in details that the web - page will need to display. This includes zipping up the file in order to - get the zip file's name and size. - """ - self.common.log("Web", "set_file_info") - self.cancel_compression = False - - # build file info list - self.file_info = {'files': [], 'dirs': []} - for filename in filenames: - info = { - 'filename': filename, - 'basename': os.path.basename(filename.rstrip('/')) - } - if os.path.isfile(filename): - info['size'] = os.path.getsize(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['files'].append(info) - if os.path.isdir(filename): - info['size'] = self.common.dir_size(filename) - info['size_human'] = self.common.human_readable_filesize(info['size']) - self.file_info['dirs'].append(info) - self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) - self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) - - # Check if there's only 1 file and no folders - if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.is_zipped = False - self.download_filename = self.file_info['files'][0]['filename'] - self.download_filesize = self.file_info['files'][0]['size'] - else: - # Zip up the files and folders - self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) - self.download_filename = self.zip_writer.zip_filename - for info in self.file_info['files']: - self.zip_writer.add_file(info['filename']) - # Canceling early? - if self.cancel_compression: - self.zip_writer.close() - return False - - for info in self.file_info['dirs']: - if not self.zip_writer.add_dir(info['filename']): - return False - - self.zip_writer.close() - self.download_filesize = os.path.getsize(self.download_filename) - self.is_zipped = True - - return True - def _safe_select_jinja_autoescape(self, filename): if filename is None: return True diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 8712653b..5845b30a 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -34,7 +34,7 @@ class ReceiveMode(Mode): Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, True) + self.web = Web(self.common, True, 'receive') # Server status self.server_status.set_mode('receive') diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index aec32305..d7ed74ed 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -43,7 +43,7 @@ class ShareMode(Mode): self.compress_thread = None # Create the Web object - self.web = Web(self.common, True, False) + self.web = Web(self.common, True, 'share') # File selection self.file_selection = FileSelection(self.common) diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index dc43bf0a..4fb40bd0 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -41,7 +41,7 @@ class CompressThread(QtCore.QThread): self.mode.common.log('CompressThread', 'run') try: - if self.mode.web.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): + if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): self.success.emit() else: # Cancelled diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py index 2209a0fd..24a0e163 100644 --- a/test/test_onionshare_web.py +++ b/test/test_onionshare_web.py @@ -38,11 +38,11 @@ DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') -def web_obj(common_obj, receive_mode, num_files=0): +def web_obj(common_obj, mode, num_files=0): """ Creates a Web object, in either share mode or receive mode, ready for testing """ common_obj.load_settings() - web = Web(common_obj, False, receive_mode) + web = Web(common_obj, False, mode) web.generate_slug() web.stay_open = True web.running = True @@ -50,14 +50,14 @@ def web_obj(common_obj, receive_mode, num_files=0): web.app.testing = True # Share mode - if not receive_mode: + if mode == 'share': # Add files files = [] for i in range(num_files): with tempfile.NamedTemporaryFile(delete=False) as tmp_file: tmp_file.write(b'*' * 1024) files.append(tmp_file.name) - web.set_file_info(files) + web.share_mode.set_file_info(files) # Receive mode else: pass @@ -67,8 +67,8 @@ def web_obj(common_obj, receive_mode, num_files=0): class TestWeb: def test_share_mode(self, common_obj): - web = web_obj(common_obj, False, 3) - assert web.receive_mode is False + web = web_obj(common_obj, 'share', 3) + assert web.mode is 'share' with web.app.test_client() as c: # Load 404 pages res = c.get('/') @@ -91,7 +91,7 @@ class TestWeb: assert res.mimetype == 'application/zip' def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024): - web = web_obj(common_obj, False, 3) + web = web_obj(common_obj, 'share', 3) web.stay_open = False assert web.running == True @@ -106,7 +106,7 @@ class TestWeb: assert web.running == False def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024): - web = web_obj(common_obj, False, 3) + web = web_obj(common_obj, 'share', 3) web.stay_open = True assert web.running == True @@ -120,8 +120,8 @@ class TestWeb: assert web.running == True def test_receive_mode(self, common_obj): - web = web_obj(common_obj, True) - assert web.receive_mode is True + web = web_obj(common_obj, 'receive') + assert web.mode is 'receive' with web.app.test_client() as c: # Load 404 pages @@ -139,7 +139,7 @@ class TestWeb: assert res.status_code == 200 def test_receive_mode_allow_receiver_shutdown_on(self, common_obj): - web = web_obj(common_obj, True) + web = web_obj(common_obj, 'receive') common_obj.settings.set('receive_allow_receiver_shutdown', True) @@ -154,7 +154,7 @@ class TestWeb: assert web.running == False def test_receive_mode_allow_receiver_shutdown_off(self, common_obj): - web = web_obj(common_obj, True) + web = web_obj(common_obj, 'receive') common_obj.settings.set('receive_allow_receiver_shutdown', False) @@ -167,9 +167,9 @@ class TestWeb: # Should redirect to index, and server should still be running assert res.status_code == 302 assert web.running == True - + def test_public_mode_on(self, common_obj): - web = web_obj(common_obj, True) + web = web_obj(common_obj, 'receive') common_obj.settings.set('public_mode', True) with web.app.test_client() as c: @@ -182,9 +182,9 @@ class TestWeb: res = c.get('/{}'.format(web.slug)) data2 = res.get_data() assert res.status_code == 404 - + def test_public_mode_off(self, common_obj): - web = web_obj(common_obj, True) + web = web_obj(common_obj, 'receive') common_obj.settings.set('public_mode', False) with web.app.test_client() as c: -- cgit v1.2.3-54-g00ecf From a86681e9038fffcd6d6cde66ed1cb10c87783d2f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 11:19:36 -0700 Subject: Refactor the CLI main function to explicitly use 'share' or 'receive' mode --- onionshare/__init__.py | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 9c390fa8..64655f4a 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -65,13 +65,18 @@ def main(cwd=None): receive = bool(args.receive) config = args.config + if receive: + mode = 'receive' + else: + mode = 'share' + # Make sure filenames given if not using receiver mode - if not receive and len(filenames) == 0: + if mode == 'share' and len(filenames) == 0: print(strings._('no_filenames')) sys.exit() # Validate filenames - if not receive: + if mode == 'share': valid = True for filename in filenames: if not os.path.isfile(filename) and not os.path.isdir(filename): @@ -90,7 +95,7 @@ def main(cwd=None): common.debug = debug # Create the Web object - web = Web(common, False, receive) + web = Web(common, False, mode) # Start the Onion object onion = Onion(common) @@ -116,21 +121,22 @@ def main(cwd=None): print(e.args[0]) sys.exit() - # Prepare files to share - print(strings._("preparing_files")) - try: - web.share_mode.set_file_info(filenames) - if web.is_zipped: - app.cleanup_filenames.append(web.download_filename) - except OSError as e: - print(e.strerror) - sys.exit(1) - - # Warn about sending large files over Tor - if web.download_filesize >= 157286400: # 150mb - print('') - print(strings._("large_filesize")) - print('') + if mode == 'share': + # Prepare files to share + print(strings._("preparing_files")) + try: + web.share_mode.set_file_info(filenames) + if web.is_zipped: + app.cleanup_filenames.append(web.download_filename) + except OSError as e: + print(e.strerror) + sys.exit(1) + + # Warn about sending large files over Tor + if web.download_filesize >= 157286400: # 150mb + print('') + print(strings._("large_filesize")) + print('') # Start OnionShare http service in new thread t = threading.Thread(target=web.start, args=(app.port, stay_open, common.settings.get('public_mode'), common.settings.get('slug'))) @@ -158,7 +164,7 @@ def main(cwd=None): url = 'http://{0:s}/{1:s}'.format(app.onion_host, web.slug) print('') - if receive: + if mode == 'receive': print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir'))) print('') print(strings._('receive_mode_warning')) -- cgit v1.2.3-54-g00ecf From 28fd67cbccfa205aeffee710b6ec3e5f5e6bf0b2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 11:36:19 -0700 Subject: Move more mode-specific logic out of the Web class and into the approprate mode web classes --- onionshare/__init__.py | 17 +++--- onionshare/web/receive_mode.py | 7 ++- onionshare/web/share_mode.py | 103 +++++++++++++++++++------------- onionshare/web/web.py | 28 ++------- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/share_mode/__init__.py | 10 ++-- onionshare_gui/share_mode/threads.py | 4 +- 7 files changed, 87 insertions(+), 84 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 64655f4a..9e3fefdd 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -126,14 +126,14 @@ def main(cwd=None): print(strings._("preparing_files")) try: web.share_mode.set_file_info(filenames) - if web.is_zipped: - app.cleanup_filenames.append(web.download_filename) + if web.share_mode.is_zipped: + app.cleanup_filenames.append(web.share_mode.download_filename) except OSError as e: print(e.strerror) sys.exit(1) # Warn about sending large files over Tor - if web.download_filesize >= 157286400: # 150mb + if web.share_mode.download_filesize >= 157286400: # 150mb print('') print(strings._("large_filesize")) print('') @@ -193,11 +193,12 @@ def main(cwd=None): if app.shutdown_timeout > 0: # if the shutdown timer was set and has run out, stop the server if not app.shutdown_timer.is_alive(): - # If there were no attempts to download the share, or all downloads are done, we can stop - if web.download_count == 0 or web.done: - print(strings._("close_on_timeout")) - web.stop(app.port) - break + if mode == 'share': + # If there were no attempts to download the share, or all downloads are done, we can stop + if web.share_mode.download_count == 0 or web.done: + print(strings._("close_on_timeout")) + web.stop(app.port) + break # Allow KeyboardInterrupt exception to be handled with threads # https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception time.sleep(0.2) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index ab5f5f13..3784ebf8 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -14,6 +14,9 @@ class ReceiveModeWeb(object): """ def __init__(self, web): self.web = web + + self.upload_count = 0 + self.define_routes() def define_routes(self): @@ -243,8 +246,8 @@ class ReceiveModeRequest(Request): self.progress = {} # Create an upload_id, attach it to the request - self.upload_id = self.web.upload_count - self.web.upload_count += 1 + self.upload_id = self.upload_count + self.upload_count += 1 # Figure out the content length try: diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index c8a411bb..21f0d1e5 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -14,6 +14,25 @@ class ShareModeWeb(object): """ def __init__(self, web): self.web = web + + # Information about the file to be shared + self.file_info = [] + self.is_zipped = False + self.download_filename = None + self.download_filesize = None + self.zip_writer = None + + self.download_count = 0 + + # If "Stop After First Download" is checked (stay_open == False), only allow + # one download at a time. + self.download_in_progress = False + + # If the client closes the OnionShare window while a download is in progress, + # it should immediately stop serving the file. The client_cancel global is + # used to tell the download function that the client is canceling the download. + self.client_cancel = False + self.define_routes() def define_routes(self): @@ -39,7 +58,7 @@ class ShareModeWeb(object): # Deny new downloads if "Stop After First Download" is checked and there is # currently a download - deny_download = not self.web.stay_open and self.web.download_in_progress + deny_download = not self.web.stay_open and self.download_in_progress if deny_download: r = make_response(render_template('denied.html')) return self.web.add_security_headers(r) @@ -49,20 +68,20 @@ class ShareModeWeb(object): r = make_response(render_template( 'send.html', slug=self.web.slug, - file_info=self.web.file_info, - filename=os.path.basename(self.web.download_filename), - filesize=self.web.download_filesize, - filesize_human=self.web.common.human_readable_filesize(self.web.download_filesize), - is_zipped=self.web.is_zipped)) + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.web.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) else: # If download is allowed to continue, serve download page r = make_response(render_template( 'send.html', - file_info=self.web.file_info, - filename=os.path.basename(self.web.download_filename), - filesize=self.web.download_filesize, - filesize_human=self.web.common.human_readable_filesize(self.web.download_filesize), - is_zipped=self.web.is_zipped)) + file_info=self.file_info, + filename=os.path.basename(self.download_filename), + filesize=self.download_filesize, + filesize_human=self.web.common.human_readable_filesize(self.download_filesize), + is_zipped=self.is_zipped)) return self.web.add_security_headers(r) @self.web.app.route("//download") @@ -82,14 +101,14 @@ class ShareModeWeb(object): """ # Deny new downloads if "Stop After First Download" is checked and there is # currently a download - deny_download = not self.web.stay_open and self.web.download_in_progress + deny_download = not self.web.stay_open and self.download_in_progress if deny_download: r = make_response(render_template('denied.html')) return self.web.add_security_headers(r) # Each download has a unique id - download_id = self.web.download_count - self.web.download_count += 1 + download_id = self.download_count + self.download_count += 1 # Prepare some variables to use inside generate() function below # which is outside of the request context @@ -101,25 +120,25 @@ class ShareModeWeb(object): 'id': download_id} ) - dirname = os.path.dirname(self.web.download_filename) - basename = os.path.basename(self.web.download_filename) + dirname = os.path.dirname(self.download_filename) + basename = os.path.basename(self.download_filename) def generate(): # The user hasn't canceled the download - self.web.client_cancel = False + self.client_cancel = False # Starting a new download if not self.web.stay_open: - self.web.download_in_progress = True + self.download_in_progress = True chunk_size = 102400 # 100kb - fp = open(self.web.download_filename, 'rb') + fp = open(self.download_filename, 'rb') self.web.done = False canceled = False while not self.web.done: # The user has canceled the download, so stop serving the file - if self.web.client_cancel: + if self.client_cancel: self.web.add_request(self.web.REQUEST_CANCELED, path, { 'id': download_id }) @@ -134,7 +153,7 @@ class ShareModeWeb(object): # tell GUI the progress downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.web.download_filesize) * 100 + percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) if not self.web.is_gui or self.web.common.platform == 'Linux' or self.web.common.platform == 'BSD': @@ -164,7 +183,7 @@ class ShareModeWeb(object): # Download is finished if not self.web.stay_open: - self.web.download_in_progress = False + self.download_in_progress = False # Close the server, if necessary if not self.web.stay_open and not canceled: @@ -178,7 +197,7 @@ class ShareModeWeb(object): pass r = Response(generate()) - r.headers.set('Content-Length', self.web.download_filesize) + r.headers.set('Content-Length', self.download_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) r = self.web.add_security_headers(r) # guess content type @@ -197,7 +216,7 @@ class ShareModeWeb(object): self.web.cancel_compression = False # build file info list - self.web.file_info = {'files': [], 'dirs': []} + self.file_info = {'files': [], 'dirs': []} for filename in filenames: info = { 'filename': filename, @@ -206,37 +225,37 @@ class ShareModeWeb(object): if os.path.isfile(filename): info['size'] = os.path.getsize(filename) info['size_human'] = self.web.common.human_readable_filesize(info['size']) - self.web.file_info['files'].append(info) + self.file_info['files'].append(info) if os.path.isdir(filename): info['size'] = self.web.common.dir_size(filename) info['size_human'] = self.web.common.human_readable_filesize(info['size']) - self.web.file_info['dirs'].append(info) - self.web.file_info['files'] = sorted(self.web.file_info['files'], key=lambda k: k['basename']) - self.web.file_info['dirs'] = sorted(self.web.file_info['dirs'], key=lambda k: k['basename']) + self.file_info['dirs'].append(info) + self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) + self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) # Check if there's only 1 file and no folders - if len(self.web.file_info['files']) == 1 and len(self.web.file_info['dirs']) == 0: - self.web.is_zipped = False - self.web.download_filename = self.web.file_info['files'][0]['filename'] - self.web.download_filesize = self.web.file_info['files'][0]['size'] + if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: + self.is_zipped = False + self.download_filename = self.file_info['files'][0]['filename'] + self.download_filesize = self.file_info['files'][0]['size'] else: # Zip up the files and folders - self.web.zip_writer = ZipWriter(self.web.common, processed_size_callback=processed_size_callback) - self.web.download_filename = self.web.zip_writer.zip_filename - for info in self.web.file_info['files']: - self.web.zip_writer.add_file(info['filename']) + self.zip_writer = ZipWriter(self.web.common, processed_size_callback=processed_size_callback) + self.download_filename = self.zip_writer.zip_filename + for info in self.file_info['files']: + self.zip_writer.add_file(info['filename']) # Canceling early? if self.web.cancel_compression: - self.web.zip_writer.close() + self.zip_writer.close() return False - for info in self.web.file_info['dirs']: - if not self.web.zip_writer.add_dir(info['filename']): + for info in self.file_info['dirs']: + if not self.zip_writer.add_dir(info['filename']): return False - self.web.zip_writer.close() - self.web.download_filesize = os.path.getsize(self.web.download_filename) - self.web.is_zipped = True + self.zip_writer.close() + self.download_filesize = os.path.getsize(self.download_filename) + self.is_zipped = True return True diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 7959ae0f..9046154a 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -73,13 +73,6 @@ class Web(object): # Monkey-patch in the fix from https://github.com/pallets/flask/commit/99c99c4c16b1327288fd76c44bc8635a1de452bc Flask.select_jinja_autoescape = self._safe_select_jinja_autoescape - # Information about the file - self.file_info = [] - self.is_zipped = False - self.download_filename = None - self.download_filesize = None - self.zip_writer = None - self.security_headers = [ ('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'), ('X-Frame-Options', 'DENY'), @@ -90,25 +83,11 @@ class Web(object): ] self.q = queue.Queue() - self.slug = None - - self.download_count = 0 - self.upload_count = 0 - self.error404_count = 0 - # If "Stop After First Download" is checked (stay_open == False), only allow - # one download at a time. - self.download_in_progress = False - self.done = False - # If the client closes the OnionShare window while a download is in progress, - # it should immediately stop serving the file. The client_cancel global is - # used to tell the download function that the client is canceling the download. - self.client_cancel = False - # shutting down the server only works within the context of flask, so the easiest way to do it is over http self.shutdown_slug = self.common.random_string(16) @@ -254,9 +233,10 @@ class Web(object): Stop the flask web server by loading /shutdown. """ - # If the user cancels the download, let the download function know to stop - # serving the file - self.client_cancel = True + if self.mode == 'share': + # If the user cancels the download, let the download function know to stop + # serving the file + self.share_mode.client_cancel = True # To stop flask, load http://127.0.0.1://shutdown if self.running: diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 5845b30a..590dec65 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -100,7 +100,7 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters - self.web.upload_count = 0 + self.web.receive_mode.upload_count = 0 self.web.error404_count = 0 # Hide and reset the uploads if we have previously shared diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index d7ed74ed..52ec672e 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -125,7 +125,7 @@ class ShareMode(Mode): The shutdown timer expired, should we stop the server? Returns a bool """ # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.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_label.setText(strings._('close_on_timeout', True)) return True @@ -139,7 +139,7 @@ class ShareMode(Mode): Starting the server. """ # Reset web counters - self.web.download_count = 0 + self.web.share_mode.download_count = 0 self.web.error404_count = 0 # Hide and reset the downloads if we have previously shared @@ -177,7 +177,7 @@ class ShareMode(Mode): self._zip_progress_bar = None # Warn about sending large files over Tor - if self.web.download_filesize >= 157286400: # 150mb + if self.web.share_mode.download_filesize >= 157286400: # 150mb self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.show() @@ -229,7 +229,7 @@ class ShareMode(Mode): """ Handle REQUEST_STARTED event. """ - self.downloads.add(event["data"]["id"], self.web.download_filesize) + self.downloads.add(event["data"]["id"], self.web.share_mode.download_filesize) self.downloads_in_progress += 1 self.update_downloads_in_progress() @@ -242,7 +242,7 @@ class ShareMode(Mode): self.downloads.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? - if event["data"]["bytes"] == self.web.download_filesize: + if event["data"]["bytes"] == self.web.share_mode.download_filesize: self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True)) # Update the total 'completed downloads' info diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index 4fb40bd0..6e114d62 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -47,8 +47,8 @@ class CompressThread(QtCore.QThread): # Cancelled pass - if self.mode.web.is_zipped: - self.mode.app.cleanup_filenames.append(self.mode.web.download_filename) + if self.mode.web.share_mode.is_zipped: + self.mode.app.cleanup_filenames.append(self.mode.web.share_mode.download_filename) except OSError as e: self.error.emit(e.strerror) -- cgit v1.2.3-54-g00ecf From 98aae9d83b73d150a13efe6d315b4e5dd3b10cb2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 11:41:49 -0700 Subject: Pass common into ShareModeWeb and ReceiveModeWeb --- onionshare/web/receive_mode.py | 35 +++++++++++++++++++---------------- onionshare/web/share_mode.py | 29 ++++++++++++++++------------- onionshare/web/web.py | 5 +++-- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 3784ebf8..c422d74e 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -12,7 +12,10 @@ class ReceiveModeWeb(object): """ All of the web logic for receive mode """ - def __init__(self, web): + def __init__(self, common, web): + self.common = common + self.common.log('ReceiveModeWeb', '__init__') + self.web = web self.upload_count = 0 @@ -26,7 +29,7 @@ class ReceiveModeWeb(object): def index_logic(): self.web.add_request(self.web.REQUEST_LOAD, request.path) - if self.web.common.settings.get('public_mode'): + if self.common.settings.get('public_mode'): upload_action = '/upload' close_action = '/close' else: @@ -37,7 +40,7 @@ class ReceiveModeWeb(object): 'receive.html', upload_action=upload_action, close_action=close_action, - receive_allow_receiver_shutdown=self.web.common.settings.get('receive_allow_receiver_shutdown'))) + receive_allow_receiver_shutdown=self.common.settings.get('receive_allow_receiver_shutdown'))) return self.web.add_security_headers(r) @self.web.app.route("/") @@ -47,7 +50,7 @@ class ReceiveModeWeb(object): @self.web.app.route("/") def index_public(): - if not self.web.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode'): return self.web.error404() return index_logic() @@ -59,18 +62,18 @@ class ReceiveModeWeb(object): # Make sure downloads_dir exists valid = True try: - self.web.common.validate_downloads_dir() + self.common.validate_downloads_dir() except DownloadsDirErrorCannotCreate: self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.web.common.settings.get('downloads_dir'))) + print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) valid = False except DownloadsDirErrorNotWritable: self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.web.common.settings.get('downloads_dir'))) + print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) valid = False if not valid: flash('Error uploading, please inform the OnionShare user', 'error') - if self.web.common.settings.get('public_mode'): + if self.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) @@ -83,7 +86,7 @@ class ReceiveModeWeb(object): # Automatically rename the file, if a file of the same name already exists filename = secure_filename(f.filename) filenames.append(filename) - local_path = os.path.join(self.web.common.settings.get('downloads_dir'), filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) if os.path.exists(local_path): if '.' in filename: # Add "-i", e.g. change "foo.txt" to "foo-2.txt" @@ -95,7 +98,7 @@ class ReceiveModeWeb(object): valid = False while not valid: new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.web.common.settings.get('downloads_dir'), new_filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) if os.path.exists(local_path): i += 1 else: @@ -106,7 +109,7 @@ class ReceiveModeWeb(object): valid = False while not valid: new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.web.common.settings.get('downloads_dir'), new_filename) + local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) if os.path.exists(local_path): i += 1 else: @@ -121,7 +124,7 @@ class ReceiveModeWeb(object): 'new_filename': basename }) - self.web.common.log('Web', 'receive_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) + self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) print(strings._('receive_mode_received_file').format(local_path)) f.save(local_path) @@ -133,7 +136,7 @@ class ReceiveModeWeb(object): for filename in filenames: flash('Sent {}'.format(filename), 'info') - if self.web.common.settings.get('public_mode'): + if self.common.settings.get('public_mode'): return redirect('/') else: return redirect('/{}'.format(slug_candidate)) @@ -145,13 +148,13 @@ class ReceiveModeWeb(object): @self.web.app.route("/upload", methods=['POST']) def upload_public(): - if not self.web.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode'): return self.web.error404() return upload_logic() def close_logic(slug_candidate=''): - if self.web.common.settings.get('receive_allow_receiver_shutdown'): + if self.common.settings.get('receive_allow_receiver_shutdown'): self.web.force_shutdown() r = make_response(render_template('closed.html')) self.web.add_request(self.web.REQUEST_CLOSE_SERVER, request.path) @@ -166,7 +169,7 @@ class ReceiveModeWeb(object): @self.web.app.route("/close", methods=['POST']) def close_public(): - if not self.web.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode'): return self.web.error404() return close_logic() diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 21f0d1e5..81e5a5b9 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -12,7 +12,10 @@ class ShareModeWeb(object): """ All of the web logic for share mode """ - def __init__(self, web): + def __init__(self, common, web): + self.common = common + self.common.log('ShareModeWeb', '__init__') + self.web = web # Information about the file to be shared @@ -46,7 +49,7 @@ class ShareModeWeb(object): @self.web.app.route("/") def index_public(): - if not self.web.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode'): return self.web.error404() return index_logic() @@ -71,7 +74,7 @@ class ShareModeWeb(object): file_info=self.file_info, filename=os.path.basename(self.download_filename), filesize=self.download_filesize, - filesize_human=self.web.common.human_readable_filesize(self.download_filesize), + filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) else: # If download is allowed to continue, serve download page @@ -80,7 +83,7 @@ class ShareModeWeb(object): file_info=self.file_info, filename=os.path.basename(self.download_filename), filesize=self.download_filesize, - filesize_human=self.web.common.human_readable_filesize(self.download_filesize), + filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) return self.web.add_security_headers(r) @@ -91,7 +94,7 @@ class ShareModeWeb(object): @self.web.app.route("/download") def download_public(): - if not self.web.common.settings.get('public_mode'): + if not self.common.settings.get('public_mode'): return self.web.error404() return download_logic() @@ -156,9 +159,9 @@ class ShareModeWeb(object): percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) - if not self.web.is_gui or self.web.common.platform == 'Linux' or self.web.common.platform == 'BSD': + if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': sys.stdout.write( - "\r{0:s}, {1:.2f}% ".format(self.web.common.human_readable_filesize(downloaded_bytes), percent)) + "\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent)) sys.stdout.flush() self.web.add_request(self.web.REQUEST_PROGRESS, path, { @@ -178,7 +181,7 @@ class ShareModeWeb(object): fp.close() - if self.web.common.platform != 'Darwin': + if self.common.platform != 'Darwin': sys.stdout.write("\n") # Download is finished @@ -212,7 +215,7 @@ class ShareModeWeb(object): page will need to display. This includes zipping up the file in order to get the zip file's name and size. """ - self.web.common.log("Web", "set_file_info") + self.common.log("ShareModeWeb", "set_file_info") self.web.cancel_compression = False # build file info list @@ -224,11 +227,11 @@ class ShareModeWeb(object): } if os.path.isfile(filename): info['size'] = os.path.getsize(filename) - info['size_human'] = self.web.common.human_readable_filesize(info['size']) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['files'].append(info) if os.path.isdir(filename): - info['size'] = self.web.common.dir_size(filename) - info['size_human'] = self.web.common.human_readable_filesize(info['size']) + info['size'] = self.common.dir_size(filename) + info['size_human'] = self.common.human_readable_filesize(info['size']) self.file_info['dirs'].append(info) self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename']) self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename']) @@ -240,7 +243,7 @@ class ShareModeWeb(object): self.download_filesize = self.file_info['files'][0]['size'] else: # Zip up the files and folders - self.zip_writer = ZipWriter(self.web.common, processed_size_callback=processed_size_callback) + self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) self.download_filename = self.zip_writer.zip_filename for info in self.file_info['files']: self.zip_writer.add_file(info['filename']) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 9046154a..52c4da16 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -43,6 +43,7 @@ class Web(object): def __init__(self, common, is_gui, mode='share'): self.common = common + self.common.log('Web', '__init__', 'is_gui={}, mode={}'.format(is_gui, mode)) # The flask app self.app = Flask(__name__, @@ -101,9 +102,9 @@ class Web(object): self.share_mode = None self.receive_mode = None if self.mode == 'receive': - self.receive_mode = ReceiveModeWeb(self) + self.receive_mode = ReceiveModeWeb(self.common, self) elif self.mode == 'share': - self.share_mode = ShareModeWeb(self) + self.share_mode = ShareModeWeb(self.common, self) def define_common_routes(self): -- cgit v1.2.3-54-g00ecf From 2a309af6801ad6a54b3da89d2687a56c9200a517 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 12:29:23 -0700 Subject: If only sharing one file, compress it with gzip, and serve it with gzip compression if the browser supports it --- onionshare/__init__.py | 3 +- onionshare/web/share_mode.py | 62 ++++++++++++++++++++++++++++++++---- onionshare_gui/share_mode/threads.py | 3 +- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 9e3fefdd..4d6d77d0 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -126,8 +126,7 @@ def main(cwd=None): print(strings._("preparing_files")) try: web.share_mode.set_file_info(filenames) - if web.share_mode.is_zipped: - app.cleanup_filenames.append(web.share_mode.download_filename) + app.cleanup_filenames += web.share_mode.cleanup_filenames except OSError as e: print(e.strerror) sys.exit(1) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 81e5a5b9..95bc8443 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -3,6 +3,7 @@ import sys import tempfile import zipfile import mimetypes +import gzip from flask import Response, request, render_template, make_response from .. import strings @@ -23,6 +24,7 @@ class ShareModeWeb(object): self.is_zipped = False self.download_filename = None self.download_filesize = None + self.gzip_filename = None self.zip_writer = None self.download_count = 0 @@ -118,12 +120,20 @@ class ShareModeWeb(object): shutdown_func = request.environ.get('werkzeug.server.shutdown') path = request.path + # If this is a zipped file, then serve as-is. If it's not zipped, then, + # if the http client supports gzip compression, gzip the file first + # and serve that + use_gzip = (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + if use_gzip: + file_to_download = self.gzip_filename + else: + file_to_download = self.download_filename + # Tell GUI the download started self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': download_id} - ) + 'id': download_id + }) - dirname = os.path.dirname(self.download_filename) basename = os.path.basename(self.download_filename) def generate(): @@ -136,7 +146,7 @@ class ShareModeWeb(object): chunk_size = 102400 # 100kb - fp = open(self.download_filename, 'rb') + fp = open(file_to_download, 'rb') self.web.done = False canceled = False while not self.web.done: @@ -200,7 +210,11 @@ class ShareModeWeb(object): pass r = Response(generate()) - r.headers.set('Content-Length', self.download_filesize) + if use_gzip: + r.headers.set('Content-Encoding', 'gzip') + r.headers.set('Content-Length', os.path.getsize(self.gzip_filename)) + else: + r.headers.set('Content-Length', self.download_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) r = self.web.add_security_headers(r) # guess content type @@ -218,6 +232,8 @@ class ShareModeWeb(object): self.common.log("ShareModeWeb", "set_file_info") self.web.cancel_compression = False + self.cleanup_filenames = [] + # build file info list self.file_info = {'files': [], 'dirs': []} for filename in filenames: @@ -238,9 +254,18 @@ class ShareModeWeb(object): # Check if there's only 1 file and no folders if len(self.file_info['files']) == 1 and len(self.file_info['dirs']) == 0: - self.is_zipped = False self.download_filename = self.file_info['files'][0]['filename'] self.download_filesize = self.file_info['files'][0]['size'] + + # Compress the file with gzip now, so we don't have to do it on each request + self.gzip_filename = tempfile.mkstemp('wb+')[1] + self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback) + + # Make sure the gzip file gets cleaned up when onionshare stops + self.cleanup_filenames.append(self.gzip_filename) + + self.is_zipped = False + else: # Zip up the files and folders self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback) @@ -258,10 +283,35 @@ class ShareModeWeb(object): self.zip_writer.close() self.download_filesize = os.path.getsize(self.download_filename) + + # Make sure the zip file gets cleaned up when onionshare stops + self.cleanup_filenames.append(self.zip_writer.zip_filename) + self.is_zipped = True return True + def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): + """ + Compress a file with gzip, without loading the whole thing into memory + Thanks: https://stackoverflow.com/questions/27035296/python-how-to-gzip-a-large-text-file-without-memoryerror + """ + bytes_processed = 0 + blocksize = 1 << 16 # 64kB + with open(input_filename, 'rb') as input_file: + output_file = gzip.open(output_filename, 'wb', level) + while True: + if processed_size_callback is not None: + processed_size_callback(bytes_processed) + + block = input_file.read(blocksize) + if len(block) == 0: + break + output_file.write(block) + bytes_processed += blocksize + + output_file.close() + class ZipWriter(object): """ diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index 6e114d62..d6022746 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -47,8 +47,7 @@ class CompressThread(QtCore.QThread): # Cancelled pass - if self.mode.web.share_mode.is_zipped: - self.mode.app.cleanup_filenames.append(self.mode.web.share_mode.download_filename) + self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames except OSError as e: self.error.emit(e.strerror) -- cgit v1.2.3-54-g00ecf From 44f408c9ac46094c3f987de4313afbf156710c8a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 13:38:01 -0700 Subject: Remove unused wait_for_hs string, and change tests to test with a different string. Also remove a few other unused strings --- share/locale/cs.json | 1 - share/locale/da.json | 3 --- share/locale/de.json | 1 - share/locale/en.json | 1 - share/locale/eo.json | 1 - share/locale/es.json | 1 - share/locale/fi.json | 1 - share/locale/fr.json | 1 - share/locale/it.json | 1 - share/locale/nl.json | 3 --- share/locale/tr.json | 1 - test/test_onionshare_strings.py | 12 ++---------- 12 files changed, 2 insertions(+), 25 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 40e48f87..a29c8b8e 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -1,7 +1,6 @@ { "config_onion_service": "Nastavuji onion service na portu {0:d}.", "preparing_files": "Připravuji soubory ke sdílení.", - "wait_for_hs": "Čekám na HS až bude připravena:", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", diff --git a/share/locale/da.json b/share/locale/da.json index 00539212..75416989 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,7 +1,6 @@ { "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", "preparing_files": "Forbereder filer som skal deles.", - "wait_for_hs": "Venter på at HS bliver klar:", "give_this_url": "Giv denne URL til personen du sender filen til:", "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", @@ -53,9 +52,7 @@ "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_label": "Usynlig (avanceret)", "gui_settings_stealth_option": "Opret usynlige onion-tjenester", - "gui_settings_stealth_option_details": "Det gør OnionShare mere sikker, men også mere besværlig for modtageren at oprette forbindelse til den.
    Mere information.", "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", "gui_settings_autoupdate_label": "Søg efter opdateringer", "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer", diff --git a/share/locale/de.json b/share/locale/de.json index 6c0fa861..1d0436a0 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,6 +1,5 @@ { "preparing_files": "Dateien werden vorbereitet.", - "wait_for_hs": "Warte auf HS:", "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", diff --git a/share/locale/en.json b/share/locale/en.json index e6b2b2c0..fbf83118 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -1,7 +1,6 @@ { "config_onion_service": "Configuring onion service on port {0:d}.", "preparing_files": "Preparing files to share.", - "wait_for_hs": "Waiting for HS to be ready:", "give_this_url": "Give this address to the person you're sending the file to:", "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", "give_this_url_receive": "Give this address to the people sending you files:", diff --git a/share/locale/eo.json b/share/locale/eo.json index 18e73165..1fbda9bc 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -1,7 +1,6 @@ { "config_onion_service": "Agordas onion service je pordo {0:d}.", "preparing_files": "Preparas dosierojn por kundivido.", - "wait_for_hs": "Atendas al hidden sevice por esti preta:", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", diff --git a/share/locale/es.json b/share/locale/es.json index b829540a..8c9945a8 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,6 +1,5 @@ { "preparing_files": "Preparando los archivos para compartir.", - "wait_for_hs": "Esperando a que HS esté listo:", "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", diff --git a/share/locale/fi.json b/share/locale/fi.json index 09186be8..8a360284 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -1,6 +1,5 @@ { "preparing_files": "Valmistellaan tiedostoja jaettavaksi.", - "wait_for_hs": "Odotetaan piilopalvelun valmistumista:", "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:", "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", "not_a_file": "{0:s} Ei ole tiedosto.", diff --git a/share/locale/fr.json b/share/locale/fr.json index b6f6eaa7..967e456e 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,6 +1,5 @@ { "preparing_files": "Préparation des fichiers à partager.", - "wait_for_hs": "En attente du HS:", "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", "ctrlc_to_stop": "Ctrl-C arrête le serveur", "not_a_file": "{0:s} n'est pas un fichier.", diff --git a/share/locale/it.json b/share/locale/it.json index 304e0cb9..6c55ed20 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,6 +1,5 @@ { "preparing_files": "Preparazione dei files da condividere.", - "wait_for_hs": "In attesa che l'HS sia pronto:", "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", diff --git a/share/locale/nl.json b/share/locale/nl.json index 67297ae0..833432ab 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -1,7 +1,6 @@ { "config_onion_service": "Onion service configureren op poort {0:d}.", "preparing_files": "Bestanden om te delen aan het voorbereiden.", - "wait_for_hs": "Wachten op gereed zijn van HS:", "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", @@ -51,9 +50,7 @@ "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", - "gui_settings_stealth_label": "Stealth (geavanceerd)", "gui_settings_stealth_option": "Maak stealth onion services", - "gui_settings_stealth_option_details": "Dit maakt OnionShare veiliger, maar ook lastiger voor de ontvanger om te verbinden.
    Meer informatie.", "gui_settings_autoupdate_label": "Controleer voor updates", "gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn", "gui_settings_autoupdate_timestamp": "Laatste controle: {}", diff --git a/share/locale/tr.json b/share/locale/tr.json index 7b531bd6..71f16daa 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,6 +1,5 @@ { "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", - "wait_for_hs": "GH hazır olması bekleniyor:", "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", diff --git a/test/test_onionshare_strings.py b/test/test_onionshare_strings.py index d1daa1e5..1d0b3206 100644 --- a/test/test_onionshare_strings.py +++ b/test/test_onionshare_strings.py @@ -47,22 +47,14 @@ class TestLoadStrings: self, common_obj, locale_en, sys_onionshare_dev_mode): """ load_strings() loads English by default """ strings.load_strings(common_obj) - assert strings._('wait_for_hs') == "Waiting for HS to be ready:" + assert strings._('preparing_files') == "Preparing files to share." def test_load_strings_loads_other_languages( self, common_obj, locale_fr, sys_onionshare_dev_mode): """ load_strings() loads other languages in different locales """ strings.load_strings(common_obj, "fr") - assert strings._('wait_for_hs') == "En attente du HS:" - - def test_load_partial_strings( - self, common_obj, locale_ru, sys_onionshare_dev_mode): - strings.load_strings(common_obj) - assert strings._("give_this_url") == ( - "Отправьте эту ссылку тому человеку, " - "которому вы хотите передать файл:") - assert strings._('wait_for_hs') == "Waiting for HS to be ready:" + assert strings._('preparing_files') == "Préparation des fichiers à partager." def test_load_invalid_locale( self, common_obj, locale_invalid, sys_onionshare_dev_mode): -- cgit v1.2.3-54-g00ecf From eac4e44dc5defc6496da13d9b544572204941ca3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 13:58:42 -0700 Subject: Remove no_filenames string, and instead display CLI usage if you don't specify filenames --- onionshare/__init__.py | 2 +- share/locale/en.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 51210b6b..0bc0abe5 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -67,7 +67,7 @@ def main(cwd=None): # Make sure filenames given if not using receiver mode if not receive and len(filenames) == 0: - print(strings._('no_filenames')) + parser.print_help() sys.exit() # Validate filenames diff --git a/share/locale/en.json b/share/locale/en.json index fbf83118..512a69b4 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -8,7 +8,6 @@ "ctrlc_to_stop": "Press Ctrl+C to stop the server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", - "no_filenames": "You must specify a list of files to share.", "no_available_port": "Could not start the Onion service as there was no available port.", "other_page_loaded": "Address loaded", "close_on_timeout": "Stopped because timer expired", -- cgit v1.2.3-54-g00ecf From 98fcf4d0ac50c1940e372c3c6d89f6ae111f1bfe Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 15:11:18 -0700 Subject: Remove the 'using_ephemeral' string --- onionshare/onion.py | 1 - share/locale/cs.json | 1 - share/locale/da.json | 1 - share/locale/en.json | 1 - share/locale/eo.json | 1 - share/locale/fi.json | 1 - share/locale/it.json | 1 - share/locale/nl.json | 1 - share/locale/tr.json | 1 - 9 files changed, 9 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 7a111eff..81b82923 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -427,7 +427,6 @@ class Onion(object): raise TorTooOld(strings._('error_stealth_not_supported')) print(strings._("config_onion_service").format(int(port))) - print(strings._('using_ephemeral')) if self.stealth: if self.settings.get('hidservauth_string'): diff --git a/share/locale/cs.json b/share/locale/cs.json index a29c8b8e..a595ce67 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -26,7 +26,6 @@ "gui_copied_url": "URL zkopírováno do schránky", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", "gui_please_wait": "Prosím čekejte...", - "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Uplynulý čas: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", diff --git a/share/locale/da.json b/share/locale/da.json index 75416989..d414695b 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -39,7 +39,6 @@ "gui_copied_url": "Kopierede URL til udklipsholder", "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", "gui_please_wait": "Vent venligst...", - "using_ephemeral": "Starter kortvarig Tor onion-tjeneste og afventer udgivelse", "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", diff --git a/share/locale/en.json b/share/locale/en.json index 512a69b4..d6937544 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -53,7 +53,6 @@ "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", "gui_please_wait": "Starting… Click to cancel", - "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", diff --git a/share/locale/eo.json b/share/locale/eo.json index 1fbda9bc..9902e4ae 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -26,7 +26,6 @@ "gui_copied_url": "URL kopiita en tondujon", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", "gui_please_wait": "Bonvolu atendi...", - "using_ephemeral": "Starting ephemeral Tor onion service and awaiting publication", "gui_download_upload_progress_complete": "%p%, Tempo pasinta: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", diff --git a/share/locale/fi.json b/share/locale/fi.json index 8a360284..d0ee80ff 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -21,6 +21,5 @@ "gui_canceled": "Peruutettu", "gui_copied_url": "URL-osoite kopioitu leikepöydälle", "gui_please_wait": "Odota...", - "using_ephemeral": "Käynnistetään lyhytaikainen Tor piilopalvelu ja odotetaan julkaisua", "zip_progress_bar_format": "Tiivistän tiedostoja: %p%" } diff --git a/share/locale/it.json b/share/locale/it.json index 6c55ed20..ebe2df4e 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -21,6 +21,5 @@ "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", "gui_please_wait": "Attendere prego...", - "using_ephemeral": "Avviamento del servizio nascosto Tor ephemeral e attesa della pubblicazione", "zip_progress_bar_format": "Elaborazione files: %p%" } diff --git a/share/locale/nl.json b/share/locale/nl.json index 833432ab..abd14753 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -37,7 +37,6 @@ "gui_copied_url": "URL gekopieerd naar klembord", "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", "gui_please_wait": "Moment geduld...", - "using_ephemeral": "Kortstondige Tor onion service gestart en in afwachting van publicatie", "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", diff --git a/share/locale/tr.json b/share/locale/tr.json index 71f16daa..68807410 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -21,6 +21,5 @@ "gui_canceled": "İptal edilen", "gui_copied_url": "Panoya kopyalanan URL", "gui_please_wait": "Lütfen bekleyin...", - "using_ephemeral": "Geçici Tor gizli hizmetine bakılıyor ve yayımı bekleniyor", "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" } -- cgit v1.2.3-54-g00ecf From 09ccbf4a6078a9d416eeefb5a7e7e6c36051f660 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 16:22:37 -0700 Subject: Dynamically figure out the total size of the download based on the whether or not the client making the http request accepts gzip --- onionshare/web/share_mode.py | 24 +++++++++++++++++++----- onionshare_gui/share_mode/__init__.py | 7 ++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 95bc8443..2024e732 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -25,6 +25,7 @@ class ShareModeWeb(object): self.download_filename = None self.download_filesize = None self.gzip_filename = None + self.gzip_filesize = None self.zip_writer = None self.download_count = 0 @@ -69,13 +70,18 @@ class ShareModeWeb(object): return self.web.add_security_headers(r) # If download is allowed to continue, serve download page + if self.should_use_gzip(): + filesize = self.gzip_filesize + else: + filesize = self.download_filesize + if self.web.slug: r = make_response(render_template( 'send.html', slug=self.web.slug, file_info=self.file_info, filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, + filesize=filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) else: @@ -84,7 +90,7 @@ class ShareModeWeb(object): 'send.html', file_info=self.file_info, filename=os.path.basename(self.download_filename), - filesize=self.download_filesize, + filesize=filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) return self.web.add_security_headers(r) @@ -123,7 +129,7 @@ class ShareModeWeb(object): # If this is a zipped file, then serve as-is. If it's not zipped, then, # if the http client supports gzip compression, gzip the file first # and serve that - use_gzip = (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + use_gzip = self.should_use_gzip() if use_gzip: file_to_download = self.gzip_filename else: @@ -131,7 +137,8 @@ class ShareModeWeb(object): # Tell GUI the download started self.web.add_request(self.web.REQUEST_STARTED, path, { - 'id': download_id + 'id': download_id, + 'use_gzip': use_gzip }) basename = os.path.basename(self.download_filename) @@ -212,7 +219,7 @@ class ShareModeWeb(object): r = Response(generate()) if use_gzip: r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', os.path.getsize(self.gzip_filename)) + r.headers.set('Content-Length', self.gzip_filesize) else: r.headers.set('Content-Length', self.download_filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) @@ -260,6 +267,7 @@ class ShareModeWeb(object): # Compress the file with gzip now, so we don't have to do it on each request self.gzip_filename = tempfile.mkstemp('wb+')[1] self._gzip_compress(self.download_filename, self.gzip_filename, 6, processed_size_callback) + self.gzip_filesize = os.path.getsize(self.gzip_filename) # Make sure the gzip file gets cleaned up when onionshare stops self.cleanup_filenames.append(self.gzip_filename) @@ -291,6 +299,12 @@ class ShareModeWeb(object): return True + def should_use_gzip(self): + """ + Should we use gzip for this browser? + """ + return (not self.is_zipped) and ('gzip' in request.headers.get('Accept-Encoding', '').lower()) + def _gzip_compress(self, input_filename, output_filename, level, processed_size_callback=None): """ Compress a file with gzip, without loading the whole thing into memory diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 52ec672e..ac6a1373 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -229,7 +229,11 @@ class ShareMode(Mode): """ Handle REQUEST_STARTED event. """ - self.downloads.add(event["data"]["id"], self.web.share_mode.download_filesize) + if event["data"]["use_gzip"]: + filesize = self.web.share_mode.gzip_filesize + else: + filesize = self.web.share_mode.download_filesize + self.downloads.add(event["data"]["id"], filesize) self.downloads_in_progress += 1 self.update_downloads_in_progress() @@ -388,6 +392,7 @@ class ZipProgressBar(QtWidgets.QProgressBar): def update_processed_size(self, val): self._processed_size = val + if self.processed_size < self.total_files_size: self.setValue(int((self.processed_size * 100) / self.total_files_size)) elif self.total_files_size != 0: -- cgit v1.2.3-54-g00ecf From bacd2a1be6a42e2793826480d5b4a4d19cf93311 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 16:24:22 -0700 Subject: Include onionshare.web module in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index a36fecab..94213f7c 100644 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ setup( url=url, license=license, keywords=keywords, packages=[ 'onionshare', + 'onionshare.web', 'onionshare_gui', 'onionshare_gui.share_mode', 'onionshare_gui.receive_mode' -- cgit v1.2.3-54-g00ecf From 4d125bd3dcd19de48a3c2550e04d797a4bed6668 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 17:08:11 -0700 Subject: Actually tell the GUI the progess --- onionshare/web/share_mode.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index 2024e732..d4d6aed7 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -132,8 +132,10 @@ class ShareModeWeb(object): use_gzip = self.should_use_gzip() if use_gzip: file_to_download = self.gzip_filename + filesize = self.gzip_filesize else: file_to_download = self.download_filename + filesize = self.download_filesize # Tell GUI the download started self.web.add_request(self.web.REQUEST_STARTED, path, { @@ -173,7 +175,7 @@ class ShareModeWeb(object): # tell GUI the progress downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / self.download_filesize) * 100 + percent = (1.0 * downloaded_bytes / filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -219,9 +221,7 @@ class ShareModeWeb(object): r = Response(generate()) if use_gzip: r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', self.gzip_filesize) - else: - r.headers.set('Content-Length', self.download_filesize) + r.headers.set('Content-Length', filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) r = self.web.add_security_headers(r) # guess content type -- cgit v1.2.3-54-g00ecf From a1cddeb9a956c799bbf7c823c705a48d69799309 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Sep 2018 17:42:36 -0700 Subject: Access .upload_count from the correct object after the web refactor --- onionshare/web/receive_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index c422d74e..4a6934a1 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -249,8 +249,8 @@ class ReceiveModeRequest(Request): self.progress = {} # Create an upload_id, attach it to the request - self.upload_id = self.upload_count - self.upload_count += 1 + self.upload_id = self.web.receive_mode.upload_count + self.web.receive_mode.upload_count += 1 # Figure out the content length try: -- cgit v1.2.3-54-g00ecf From f09d53952127c1b36a94033f7b573bbeb3b38550 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 16:50:39 +1000 Subject: GUI unit tests in both share and receive mode --- .travis.yml | 3 +- unit_tests/__init__.py | 0 unit_tests/conftest.py | 160 ++++++++++++ unit_tests/onionshare_receive_mode_upload_test.py | 258 ++++++++++++++++++ ...onshare_receive_mode_upload_test_public_mode.py | 258 ++++++++++++++++++ unit_tests/onionshare_share_mode_download_test.py | 287 +++++++++++++++++++++ ...onshare_share_mode_download_test_public_mode.py | 287 +++++++++++++++++++++ ...nionshare_share_mode_download_test_stay_open.py | 256 ++++++++++++++++++ unit_tests/onionshare_timer_test.py | 142 ++++++++++ unit_tests/run_unit_tests.sh | 5 + 10 files changed, 1655 insertions(+), 1 deletion(-) create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/conftest.py create mode 100644 unit_tests/onionshare_receive_mode_upload_test.py create mode 100644 unit_tests/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 unit_tests/onionshare_share_mode_download_test.py create mode 100644 unit_tests/onionshare_share_mode_download_test_public_mode.py create mode 100644 unit_tests/onionshare_share_mode_download_test_stay_open.py create mode 100644 unit_tests/onionshare_timer_test.py create mode 100755 unit_tests/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index afbaa887..71778af4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 + - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -17,5 +17,6 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ +script: cd unit_tests && bash run_unit_tests.sh after_success: - coveralls diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/unit_tests/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/unit_tests/onionshare_receive_mode_upload_test.py b/unit_tests/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..b99faac0 --- /dev/null +++ b/unit_tests/onionshare_receive_mode_upload_test.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json +import requests + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + '''Test that the info widget along top of screen is not shown because we have a file''' + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + + @pytest.mark.run(order=6) + def test_click_receive_mode(self): + '''Test that we can switch to Receive Mode by clicking the button''' + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + + @pytest.mark.run(order=7) + def test_uploads_section_is_visible(self): + '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=13) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Receiving''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + + @pytest.mark.run(order=18) + def test_web_page(self): + '''Test that the web page contains the term Select the files you want to send, then click''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.receive_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) + f.close() + + @pytest.mark.run(order=19) + def test_upload_file(self): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + + @pytest.mark.run(order=20) + def test_uploads_widget_present(self): + '''Test that the No Uploads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + + @pytest.mark.run(order=21) + def test_upload_count_incremented(self): + '''Test that the Upload Count has incremented''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + '''Test that we can upload the same file and that it gets renamed''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + '''Test that the Upload Count has incremented again''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + '''Test that the server stops when we click Stop''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py new file mode 100644 index 00000000..d309e5b1 --- /dev/null +++ b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json +import requests + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + '''Test that the info widget along top of screen is not shown because we have a file''' + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + + @pytest.mark.run(order=6) + def test_click_receive_mode(self): + '''Test that we can switch to Receive Mode by clicking the button''' + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + + @pytest.mark.run(order=7) + def test_uploads_section_is_visible(self): + '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=13) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=14) + def test_have_no_slug(self): + '''Test that we have a valid slug''' + self.assertIsNone(self.gui.share_mode.server_status.web.slug) + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Receiving''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + + @pytest.mark.run(order=18) + def test_web_page(self): + '''Test that the web page contains the term Select the files you want to send, then click''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET / HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) + f.close() + + @pytest.mark.run(order=19) + def test_upload_file(self): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + + @pytest.mark.run(order=20) + def test_uploads_widget_present(self): + '''Test that the No Uploads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + + @pytest.mark.run(order=21) + def test_upload_count_incremented(self): + '''Test that the Upload Count has incremented''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + '''Test that we can upload the same file and that it gets renamed''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + '''Test that the Upload Count has incremented again''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + '''Test that the server stops when we click Stop''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test.py b/unit_tests/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..aa8dcdaa --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_web_page(self): + '''Test that the web page contains the term Total size''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Total size' in f.read()) + f.close() + + @pytest.mark.run(order=23) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=24) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + '''Test that the server stopped automatically when we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed because download occurred''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + @pytest.mark.run(order=28) + def test_add_button_visible_again(self): + '''Test that the add button should be visible again''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_public_mode.py b/unit_tests/onionshare_share_mode_download_test_public_mode.py new file mode 100644 index 00000000..59905149 --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test_public_mode.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_no_slug(self): + '''Test that we have a valid slug''' + self.assertIsNone(self.gui.share_mode.server_status.web.slug) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_web_page(self): + '''Test that the web page contains the term Total size''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET / HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Total size' in f.read()) + f.close() + + @pytest.mark.run(order=23) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET /download HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=24) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + '''Test that the server stopped automatically when we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed because download occurred''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + @pytest.mark.run(order=28) + def test_add_button_visible_again(self): + '''Test that the add button should be visible again''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_stay_open.py b/unit_tests/onionshare_share_mode_download_test_stay_open.py new file mode 100644 index 00000000..82a0ac87 --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test_stay_open.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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": False, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=23) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=24) + def test_server_is_not_stopped(self): + '''Test that the server stayed open after we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=25) + def test_web_service_is_running(self): + '''Test that the web server is still running''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.assertEquals(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_download_count_incremented(self): + '''Test that the Download Count has incremented''' + self.assertEquals(self.gui.share_mode.downloads_completed, 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_timer_test.py b/unit_tests/onionshare_timer_test.py new file mode 100644 index 00000000..ed20c1c0 --- /dev/null +++ b/unit_tests/onionshare_timer_test.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "receive_allow_receiver_shutdown": 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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_set_timeout(self): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(120) + self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + + @pytest.mark.run(order=3) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=4) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=5) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=6) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=7) + def test_timeout_widget_hidden(self): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + + @pytest.mark.run(order=8) + def test_server_timed_out(self): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(100000) + # We should have timed out now + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=9) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/run_unit_tests.sh b/unit_tests/run_unit_tests.sh new file mode 100755 index 00000000..d15f8a6e --- /dev/null +++ b/unit_tests/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + py.test-3 $test -vvv || exit 1 +done -- cgit v1.2.3-54-g00ecf From 12838f8e9dc5824e99bc3249c99d68c0059944e2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 16:54:52 +1000 Subject: Try and make travis-friendly tests --- .travis.yml | 3 ++- unit_tests/run_unit_tests_travis.sh | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100755 unit_tests/run_unit_tests_travis.sh diff --git a/.travis.yml b/.travis.yml index 71778af4..03ae798a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: install: - pip install -r install/requirements.txt - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering + - apt-get install xvfb before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -17,6 +18,6 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ -script: cd unit_tests && bash run_unit_tests.sh +script: cd unit_tests && bash run_unit_tests_travis.sh after_success: - coveralls diff --git a/unit_tests/run_unit_tests_travis.sh b/unit_tests/run_unit_tests_travis.sh new file mode 100755 index 00000000..236c48ff --- /dev/null +++ b/unit_tests/run_unit_tests_travis.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + xvfb-run pytest $test -vvv || exit 1 +done -- cgit v1.2.3-54-g00ecf From 7c9cc76cce1c30be370e2b264b9488b5e14ca919 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 16:57:26 +1000 Subject: xvfb might already be installed? --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 03ae798a..6771eca8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ python: install: - pip install -r install/requirements.txt - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering - - apt-get install xvfb before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From 174cfbd3fb2e4c63d67be913fc0ff8ff0dc6bfce Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 17:02:16 +1000 Subject: need pytest-qt --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6771eca8..c9c58e97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering + - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering pytest-qt before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From d48d780686efbaf449ff0ef9517607718a31baf1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 17:06:02 +1000 Subject: fighting with travis... --- .travis.yml | 1 + unit_tests/run_unit_tests_travis.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9c58e97..7df1d4c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: install: - pip install -r install/requirements.txt - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering pytest-qt + - sudo apt-get install python3-pytest before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics diff --git a/unit_tests/run_unit_tests_travis.sh b/unit_tests/run_unit_tests_travis.sh index 236c48ff..ba9b5b4e 100755 --- a/unit_tests/run_unit_tests_travis.sh +++ b/unit_tests/run_unit_tests_travis.sh @@ -1,5 +1,5 @@ #!/bin/bash for test in `ls -1 | egrep ^onionshare_`; do - xvfb-run pytest $test -vvv || exit 1 + xvfb-run pytest-3 $test -vvv || exit 1 done -- cgit v1.2.3-54-g00ecf From 9a505af3bb977581c894b6b155f8e3ddeef9d7b2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 17:47:38 +1000 Subject: Add persistent slug test. Add test of clipboard contents in Share mode. Remove travis stuff that I couldn't get to work --- .travis.yml | 4 +- unit_tests/onionshare_share_mode_download_test.py | 7 +- unit_tests/onionshare_slug_persistent_test.py | 161 ++++++++++++++++++++++ unit_tests/run_unit_tests_travis.sh | 5 - 4 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 unit_tests/onionshare_slug_persistent_test.py delete mode 100755 unit_tests/run_unit_tests_travis.sh diff --git a/.travis.yml b/.travis.yml index 7df1d4c4..afbaa887 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering pytest-qt - - sudo apt-get install python3-pytest + - pip install pytest-cov coveralls flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -18,6 +17,5 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ -script: cd unit_tests && bash run_unit_tests_travis.sh after_success: - coveralls diff --git a/unit_tests/onionshare_share_mode_download_test.py b/unit_tests/onionshare_share_mode_download_test.py index aa8dcdaa..c4c8bee7 100644 --- a/unit_tests/onionshare_share_mode_download_test.py +++ b/unit_tests/onionshare_share_mode_download_test.py @@ -8,7 +8,7 @@ import zipfile import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtCore, QtWidgets, QtTest, QtGui from onionshare.common import Common from onionshare.web import Web @@ -197,8 +197,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=20) def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown''' + '''Test that the Copy URL button is shown and can be copied to clipboard''' self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + clipboard = self.gui.qtapp.clipboard() + self.assertEquals(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, self.gui.share_mode.server_status.web.slug)) @pytest.mark.run(order=21) def test_server_status_indicator_says_sharing(self): diff --git a/unit_tests/onionshare_slug_persistent_test.py b/unit_tests/onionshare_slug_persistent_test.py new file mode 100644 index 00000000..d0dcd08a --- /dev/null +++ b/unit_tests/onionshare_slug_persistent_test.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + slug = '' + + @classmethod + 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 = { + "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, + "receive_allow_receiver_shutdown": True, + "save_private_key": True, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=3) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=4) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=5) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=6) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=7) + def test_server_can_be_stopped(self): + '''Test we can stop the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_STOPPED state + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=8) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + QtTest.QTest.qWait(4000) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=9) + def test_server_started_again(self): + '''Test we can start the service again''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=10) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + + @pytest.mark.run(order=11) + def test_server_is_stopped_again(self): + '''Test that we can stop the server''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(1000) + self.assertEqual(self.gui.share_mode.server_status.status, 0) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/run_unit_tests_travis.sh b/unit_tests/run_unit_tests_travis.sh deleted file mode 100755 index ba9b5b4e..00000000 --- a/unit_tests/run_unit_tests_travis.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^onionshare_`; do - xvfb-run pytest-3 $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From cb3ed3eadde7c522faed15f9e9d8a5aa5f7f2bf2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 17:57:53 +1000 Subject: One more travis test --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index afbaa887..65eac2c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 + - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering pytest-qt pytest before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -17,5 +17,6 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ +script: xvfb-run py.test-3 unit_tests/onionshare_share_mode_download_test.py after_success: - coveralls -- cgit v1.2.3-54-g00ecf From 157dde37e3253a0d5050b68dc07e914abb5aac56 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 18:01:17 +1000 Subject: pytest --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 65eac2c5..9aece101 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,6 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ -script: xvfb-run py.test-3 unit_tests/onionshare_share_mode_download_test.py +script: xvfb-run pytest unit_tests/onionshare_share_mode_download_test.py after_success: - coveralls -- cgit v1.2.3-54-g00ecf From 4e68c62b9c0607bbfc965b033997712090022c26 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 18:07:14 +1000 Subject: Nope --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9aece101..afbaa887 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering pytest-qt pytest + - pip install pytest-cov coveralls flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -17,6 +17,5 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ -script: xvfb-run pytest unit_tests/onionshare_share_mode_download_test.py after_success: - coveralls -- cgit v1.2.3-54-g00ecf From bd4f3e5fe7817214ab2a129c740b684a2eab56db Mon Sep 17 00:00:00 2001 From: Baccount Date: Sat, 22 Sep 2018 11:18:18 -0700 Subject: Update get-tor-windows.py --- install/get-tor-windows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index 0d16dd29..e5a24be9 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -33,9 +33,9 @@ import subprocess import requests def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/torbrowser-install-8.0_en-US.exe' - exe_filename = 'torbrowser-install-8.0_en-US.exe' - expected_exe_sha256 = '0682b44eff5877dfc2fe2fdd5b46e678d47adad86d564e7cb6654c5f60eb1ed2' + exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/torbrowser-install-8.0.1_en-US.exe' + exe_filename = 'torbrowser-install-8.0.1_en-US.exe' + expected_exe_sha256 = 'bdf81d4282b991a6425c213c7b03b3f5c1f17bb02986b7fe9a1891e577e51639' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') -- cgit v1.2.3-54-g00ecf From 726a3e4b09a3b0f0d5421701c3926690f357ba92 Mon Sep 17 00:00:00 2001 From: Baccount Date: Sat, 22 Sep 2018 11:20:55 -0700 Subject: Upgrade Tor to 0.3.4.8 --- install/get-tor-osx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 1d2c6f56..ae20fd74 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -35,9 +35,9 @@ import subprocess import requests def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0/TorBrowser-8.0-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.0-osx64_en-US.dmg' - expected_dmg_sha256 = '15603ae7b3a1942863c98acc92f509e4409db48fe22c9acae6b15c9cb9bf3088' + dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/TorBrowser-8.0.1-osx64_en-US.dmg' + dmg_filename = 'TorBrowser-8.0.1-osx64_en-US.dmg' + expected_dmg_sha256 = 'fb1be2a0f850a65bae38747c3abbf9061742c5d7799e1693405078aaf38d2b08' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) -- cgit v1.2.3-54-g00ecf From 180a61d4bacf79f06f91d3394444ebda45e6af07 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Sun, 23 Sep 2018 06:22:05 +0200 Subject: Language rework --- share/locale/en.json | 248 +++++++++++++++++++++++++-------------------------- 1 file changed, 124 insertions(+), 124 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index d6937544..a36fc0a4 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -1,184 +1,184 @@ { - "config_onion_service": "Configuring onion service on port {0:d}.", - "preparing_files": "Preparing files to share.", - "give_this_url": "Give this address to the person you're sending the file to:", - "give_this_url_stealth": "Give this address and HidServAuth line to the person you're sending the file to:", - "give_this_url_receive": "Give this address to the people sending you files:", - "give_this_url_receive_stealth": "Give this address and HidServAuth line to the people sending you files:", + "config_onion_service": "Setting up onion service on port {0:d}.", + "preparing_files": "Preparing share.", + "give_this_url": "Give this address to the recipient:", + "give_this_url_stealth": "Give this inbox address and HidServAuth line to the recipient:", + "give_this_url_receive": "Give this inbox address to the sender:", + "give_this_url_receive_stealth": "Give this inbox address and HidServAuth to the sender:", "ctrlc_to_stop": "Press Ctrl+C to stop the server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", - "no_available_port": "Could not start the Onion service as there was no available port.", - "other_page_loaded": "Address loaded", - "close_on_timeout": "Stopped because timer expired", - "closing_automatically": "Stopped because download finished", - "timeout_download_still_running": "Waiting for download to complete", - "large_filesize": "Warning: Sending large files could take hours", + "no_available_port": "Could not find an available port to start the onion service", + "other_page_loaded": "Inbox address loaded", + "close_on_timeout": "Stopped because timeframe expired", + "closing_automatically": "Stopped because you have the complete share", + "timeout_download_still_running": "Awaiting completion of share transfer to you", + "large_filesize": "Warning: Sending a large share could take hours", "systray_menu_exit": "Quit", - "systray_download_started_title": "OnionShare Download Started", - "systray_download_started_message": "A user started downloading your files", - "systray_download_completed_title": "OnionShare Download Finished", - "systray_download_completed_message": "The user finished downloading your files", - "systray_download_canceled_title": "OnionShare Download Canceled", - "systray_download_canceled_message": "The user canceled the download", - "systray_upload_started_title": "OnionShare Upload Started", - "systray_upload_started_message": "A user started uploading files to your computer", - "help_local_only": "Do not attempt to use Tor: For development only", - "help_stay_open": "Keep onion service running after download has finished", - "help_shutdown_timeout": "Shut down the onion service after N seconds", - "help_stealth": "Create stealth onion service (advanced)", - "help_receive": "Receive files instead of sending them", - "help_debug": "Log application errors to stdout, and log web errors to disk", + "systray_download_started_title": "Sending…", + "systray_download_started_message": "Your share is being sent.", + "systray_download_completed_title": "Share sent", + "systray_download_completed_message": "Your share has been sent.", + "systray_download_canceled_title": "Share cancelled", + "systray_download_canceled_message": "The recipient cancelled the share.", + "systray_upload_started_title": "Receiving…", + "systray_upload_started_message": "Started share transfer to inbox.", + "help_local_only": "Avoid using Tor: (Only for development)", + "help_stay_open": "Keep sending after one completion", + "help_shutdown_timeout": "Stop sharing after a given amount of seconds", + "help_stealth": "Make share that requires HidServAuth (advanced)", + "help_receive": "Receive shares instead of sending them", + "help_debug": "Log OnionShare errors to stdout, and web errors to disk", "help_filename": "List of files or folders to share", - "help_config": "Path to a custom JSON config file (optional)", + "help_config": "Custom JSON config file location (optional)", "gui_drag_and_drop": "Drag and drop files and folders\nto start sharing", "gui_add": "Add", "gui_delete": "Delete", "gui_choose_items": "Choose", - "gui_share_start_server": "Start Sharing", - "gui_share_stop_server": "Stop Sharing", - "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Share will expire automatically at {}", - "gui_receive_start_server": "Start Receive Mode", - "gui_receive_stop_server": "Stop Receive Mode", - "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "Receive mode will expire automatically at {}", + "gui_share_start_server": "Share", + "gui_share_stop_server": "Stop", + "gui_share_stop_server_shutdown_timeout": "Stop sharing ({}s remaining)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Allowed share timeframe ends {}", + "gui_receive_start_server": "Receive", + "gui_receive_stop_server": "Stop", + "gui_receive_stop_server_shutdown_timeout": "Stop receiving ({}s remaining)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Allowed receive timeframe ends {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "Download History", - "gui_no_downloads": "No downloads yet.", + "gui_downloads": "History of sent shares", + "gui_no_downloads": "None yet.", "gui_canceled": "Canceled", - "gui_copied_url_title": "Copied OnionShare address", - "gui_copied_url": "The OnionShare address has been copied to clipboard", - "gui_copied_hidservauth_title": "Copied HidServAuth", - "gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard", - "gui_please_wait": "Starting… Click to cancel", - "gui_download_upload_progress_complete": "%p%, Time Elapsed: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (Computing ETA)", + "gui_copied_url_title": "Copied", + "gui_copied_url": "OnionShare address copied to clipboard", + "gui_copied_hidservauth_title": "Copied", + "gui_copied_hidservauth": "HidServAuth line copied to clipboard", + "gui_please_wait": "Starting… Click to cancel.", + "gui_download_upload_progress_complete": "%p%, {0:s} elapsed.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculating end time.)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_quit_title": "Transfer in Progress", - "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", - "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?", + "gui_quit_title": "Not so fast", + "gui_share_quit_warning": "Quitting now means files en route elsewhere won't finish.", + "gui_receive_quit_warning": "Quitting now means files en route to you won't finish.", "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", - "error_rate_limit": "An attacker might be trying to guess your address. To prevent this, OnionShare has automatically stopped the server. To share the files you must start it again and share the new address.", - "zip_progress_bar_format": "Compressing files: %p%", - "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare requires at least Tor 0.2.7.1 and at least python3-stem 1.4.0.", + "error_rate_limit": "Too many wrong attempts on your sharing address means someone could be trying to guess it. Start OnionShare again and send a new address to share.", + "zip_progress_bar_format": "Making share smaller: %p%", + "error_stealth_not_supported": "To create shares that require knowing HidServAuth, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.", "gui_settings_window_title": "Settings", - "gui_settings_whats_this": "what's this?", - "gui_settings_stealth_option": "Create stealth onion services (legacy)", - "gui_settings_stealth_hidservauth_string": "You have saved the private key for reuse, so your HidServAuth string is also reused.\nClick below to copy the HidServAuth.", - "gui_settings_autoupdate_label": "Check for upgrades", - "gui_settings_autoupdate_option": "Notify me when upgrades are available", + "gui_settings_whats_this": "What's this?", + "gui_settings_stealth_option": "Create share that requires knowing HidServAuth (legacy)", + "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now\nclick to copy your HidServAuth.", + "gui_settings_autoupdate_label": "Check for new version", + "gui_settings_autoupdate_option": "Notify me when a new version is available", "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", - "gui_settings_autoupdate_check_button": "Check For Upgrades", + "gui_settings_autoupdate_check_button": "Check for New Version", "gui_settings_general_label": "General settings", "gui_settings_sharing_label": "Sharing settings", - "gui_settings_close_after_first_download_option": "Stop sharing after first download", + "gui_settings_close_after_first_download_option": "Stop sharing after first completion", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", - "gui_settings_connection_type_bundled_option": "Use the Tor version that is bundled with OnionShare", - "gui_settings_connection_type_automatic_option": "Attempt automatic configuration with Tor Browser", + "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", + "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", "gui_settings_connection_type_control_port_option": "Connect using control port", "gui_settings_connection_type_socket_file_option": "Connect using socket file", - "gui_settings_connection_type_test_button": "Test Tor Settings", + "gui_settings_connection_type_test_button": "Test Connection to Tor", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_socks_label": "SOCKS port", - "gui_settings_authenticate_label": "Tor authentication settings", - "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", + "gui_settings_authenticate_label": "Tor identification settings", + "gui_settings_authenticate_no_auth_option": "None or cookie based", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", - "gui_settings_tor_bridges": "Tor Bridge support", + "gui_settings_tor_bridges": "Tor bridge support", "gui_settings_tor_bridges_no_bridges_radio_option": "Don't use bridges", "gui_settings_tor_bridges_obfs4_radio_option": "Use built-in obfs4 pluggable transports", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Use built-in obfs4 pluggable transports (requires obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Use built-in meek_lite (Azure) pluggable transports", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Use built-in meek_lite (Azure) pluggable transports (requires obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Warning: the meek_lite bridges are very costly for the Tor Project to run!

    You should only use meek_lite bridges if you are having trouble connecting to Tor directly, via obfs4 transports or other normal bridges.", + "gui_settings_meek_lite_expensive_warning": "Warning: The meek_lite bridges are very costly for the Tor Project to run.

    Only use them if unable to connect to Tor directly, via obfs4 transports, or other normal bridges.", "gui_settings_tor_bridges_custom_radio_option": "Use custom bridges", "gui_settings_tor_bridges_custom_label": "You can get bridges from https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "None of the bridges you supplied seem to work.\nPlease try again by double-checking them or adding other ones.", + "gui_settings_tor_bridges_invalid": "None of the bridges you added work.\nDouble-check them or add others.", "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", - "gui_settings_shutdown_timeout": "Stop the share at:", - "settings_saved": "Settings saved to {}", - "settings_error_unknown": "Can't connect to Tor controller because the settings don't make sense.", - "settings_error_automatic": "Can't connect to Tor controller. Is Tor Browser running in the background? If you don't have it you can get it from:\nhttps://www.torproject.org/.", - "settings_error_socket_port": "Can't connect to Tor controller on {}:{}.", - "settings_error_socket_file": "Can't connect to Tor controller using socket file {}.", + "gui_settings_shutdown_timeout_checkbox": "Stop sharing after a given timeframe", + "gui_settings_shutdown_timeout": "Stop sharing after:", + "settings_saved": "Settings saved in {}", + "settings_error_unknown": "Can't connect to Tor controller because it knows your settings aren't making sense.", + "settings_error_automatic": "Could not connect to the Tor controller. The Tor Browser (available from https://www.torproject.org/)\nneeds to be running in the background.", + "settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.", + "settings_error_socket_file": "The Tor controller does not allow connection using the socket file {}.", "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", - "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", - "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user lacks permission to read the cookie file.", - "settings_error_bundled_tor_not_supported": "Use of the Tor version bundled with OnionShare is not supported when using developer mode on Windows or macOS.", - "settings_error_bundled_tor_timeout": "Connecting to Tor is taking too long. Maybe your computer is offline, or your system clock isn't accurate.", + "settings_error_missing_password": "Connected to the Tor controller, type in the password for it.", + "settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.", + "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", + "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", - "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", + "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports shares that require HidServAuth: {}.", "error_tor_protocol_error": "There was an error with Tor: {}", "error_tor_protocol_error_unknown": "There was an unknown error with Tor", "error_invalid_private_key": "This private key type is unsupported", "connecting_to_tor": "Connecting to the Tor network", - "update_available": "A new version of OnionShare is available. Click here to download it.

    Installed version: {}
    Latest version: {}", - "update_error_check_error": "Error checking for updates: Maybe you're not connected to Tor, or maybe the OnionShare website is down.", - "update_error_invalid_latest_version": "Error checking for updates: The OnionShare website responded saying the latest version is '{}', but that doesn't appear to be a valid version string.", - "update_not_available": "You are running the latest version of OnionShare.", - "gui_tor_connection_ask": "Would you like to open OnionShare settings to troubleshoot connecting to Tor?", - "gui_tor_connection_ask_open_settings": "Open Settings", + "update_available": "New OnionShare out. Click here to get it.

    You are using {} and the latest is {}.", + "update_error_check_error": "Could not check for new versions: The OnionShare website is saying the latest version is the unrecognizable '{}'…", + "update_error_invalid_latest_version": "Could not check for new version: Maybe you're not connected to Tor, or the OnionShare website is down?", + "update_not_available": "Running the latest OnionShare.", + "gui_tor_connection_ask": "Open the settings to sort out connection to Tor?", + "gui_tor_connection_ask_open_settings": "Yes", "gui_tor_connection_ask_quit": "Quit", - "gui_tor_connection_error_settings": "Try adjusting how OnionShare connects to the Tor network in Settings.", - "gui_tor_connection_canceled": "OnionShare could not connect to Tor.\n\nMake sure you're connected to the Internet, then re-open OnionShare to set up the Tor connection.", + "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.", + "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", "gui_tor_connection_lost": "Disconnected from Tor.", - "gui_server_started_after_timeout": "The server started after your chosen auto-timeout.\nPlease start a new share.", - "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", - "share_via_onionshare": "Share via OnionShare", + "gui_server_started_after_timeout": "The set sharing timeframe ran out before the server started.\nPlease make a new share.", + "gui_server_timeout_expired": "The chosen sharing timeframe has already expired.\nPlease update it to start sharing.", + "share_via_onionshare": "OnionShare it", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", - "gui_share_url_description": "Anyone with this link can download your files using the Tor Browser: ", - "gui_receive_url_description": "Anyone with this link can upload files to your computer using the Tor Browser: ", - "gui_url_label_persistent": "This share will not expire automatically unless a timer is set.

    Every share will have the same address (to use one-time addresses, disable persistence in Settings)", - "gui_url_label_stay_open": "This share will not expire automatically unless a timer is set.", - "gui_url_label_onetime": "This share will expire after the first download", - "gui_url_label_onetime_and_persistent": "This share will expire after the first download

    Every share will have the same address (to use one-time addresses, disable persistence in the Settings)", - "gui_status_indicator_share_stopped": "Ready to Share", + "gui_share_url_description": "Anyone with this OnionShare address can fetch your share using the Tor Browser: ", + "gui_receive_url_description": "Anyone with this link can upload files to your inbox folder using the Tor Browser: ", + "gui_url_label_persistent": "This share will not auto-expire unless a timeframe is set.

    Every subsequent share reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", + "gui_url_label_stay_open": "This share will not auto-expire unless a timeframe is set.", + "gui_url_label_onetime": "This share will expire after first completion.", + "gui_url_label_onetime_and_persistent": "This share will not auto-expire unless a timeframe is set.

    Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", + "gui_status_indicator_share_stopped": "Ready to share", "gui_status_indicator_share_working": "Starting…", "gui_status_indicator_share_started": "Sharing", - "gui_status_indicator_receive_stopped": "Ready to Receive", + "gui_status_indicator_receive_stopped": "Ready to fetch", "gui_status_indicator_receive_working": "Starting…", - "gui_status_indicator_receive_started": "Receiving", - "gui_file_info": "{} Files, {}", - "gui_file_info_single": "{} File, {}", - "info_in_progress_downloads_tooltip": "{} download(s) in progress", - "info_completed_downloads_tooltip": "{} download(s) completed", - "info_in_progress_uploads_tooltip": "{} upload(s) in progress", - "info_completed_uploads_tooltip": "{} upload(s) completed", - "error_cannot_create_downloads_dir": "Error creating downloads folder: {}", - "error_downloads_dir_not_writable": "The downloads folder isn't writable: {}", - "receive_mode_downloads_dir": "Files people send you will appear in this folder: {}", - "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can hack your computer if you open them! Only open files from people you trust, or if you know what you're doing.", - "gui_receive_mode_warning": "Some files can hack your computer if you open them!
    Only open files from people you trust, or if you know what you're doing.", + "gui_status_indicator_receive_started": "Fetching", + "gui_file_info": "{} files, {}", + "gui_file_info_single": "{} file, {}", + "info_in_progress_downloads_tooltip": "{} incoming", + "info_completed_downloads_tooltip": "{} incoming completed", + "info_in_progress_uploads_tooltip": "{} outgoing", + "info_completed_uploads_tooltip": "{} outgoing completed", + "error_cannot_create_downloads_dir": "Could not create inbox: {}", + "error_downloads_dir_not_writable": "The inbox folder is write protected: {}", + "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", + "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer, potentially taking control of it if you open them. Only open things from people you trust to know what they are doing.", + "gui_receive_mode_warning": "A share could contain files that take control of your computer if opened!
    Only open things from people you trust to know what they are doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", - "receive_mode_received_file": "Received file: {}", - "gui_mode_share_button": "Share Files", - "gui_mode_receive_button": "Receive Files", - "gui_settings_receiving_label": "Receiving settings", - "gui_settings_downloads_label": "Save files to", + "receive_mode_received_file": "Received: {}", + "gui_mode_share_button": "Share", + "gui_mode_receive_button": "Receive", + "gui_settings_receiving_label": "Reception settings", + "gui_settings_downloads_label": "Save shares to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_public_mode_checkbox": "Public mode", - "systray_close_server_title": "OnionShare Server Closed", + "systray_close_server_title": "OnionShare Server Shut Down", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", - "systray_download_page_loaded_message": "A user loaded the download page", - "systray_upload_page_loaded_message": "A user loaded the upload page", - "gui_uploads": "Upload History", - "gui_no_uploads": "No uploads yet.", - "gui_clear_history": "Clear history", - "gui_upload_in_progress": "Upload Started {}", - "gui_upload_finished_range": "Uploaded {} to {}", - "gui_upload_finished": "Uploaded {}", - "gui_open_folder_error_nautilus": "Cannot open folder the because nautilus is not available. You can find this file here: {}" + "systray_download_page_loaded_message": "Your page has been loaded", + "systray_upload_page_loaded_message": "Someone is browsing what you share", + "gui_uploads": "Sent Shares", + "gui_no_uploads": "Nothing sent yet.", + "gui_clear_history": "Clear", + "gui_upload_in_progress": "Delivery Started {}", + "gui_upload_finished_range": "Sent {} to {}", + "gui_upload_finished": "Sent {}", + "gui_open_folder_error_nautilus": "Cannot open folder without a file manager. The file is here: {}" } -- cgit v1.2.3-54-g00ecf From d773a777cc8f804ce6fb9a366eb0e43576663789 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Sun, 23 Sep 2018 06:28:31 +0200 Subject: No "expired", Allowed sharing timeframe --- share/locale/en.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index a36fc0a4..9269611d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -10,7 +10,7 @@ "not_a_readable_file": "{0:s} is not a readable file.", "no_available_port": "Could not find an available port to start the onion service", "other_page_loaded": "Inbox address loaded", - "close_on_timeout": "Stopped because timeframe expired", + "close_on_timeout": "Stopped because timeframe ran out", "closing_automatically": "Stopped because you have the complete share", "timeout_download_still_running": "Awaiting completion of share transfer to you", "large_filesize": "Warning: Sending a large share could take hours", @@ -38,7 +38,7 @@ "gui_share_start_server": "Share", "gui_share_stop_server": "Stop", "gui_share_stop_server_shutdown_timeout": "Stop sharing ({}s remaining)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Allowed share timeframe ends {}", + "gui_share_stop_server_shutdown_timeout_tooltip": "Allowed sharing timeframe ends {}", "gui_receive_start_server": "Receive", "gui_receive_stop_server": "Stop", "gui_receive_stop_server_shutdown_timeout": "Stop receiving ({}s remaining)", @@ -105,7 +105,7 @@ "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Stop sharing after a given timeframe", - "gui_settings_shutdown_timeout": "Stop sharing after:", + "gui_settings_shutdown_timeout": "Allowed sharing timeframe:", "settings_saved": "Settings saved in {}", "settings_error_unknown": "Can't connect to Tor controller because it knows your settings aren't making sense.", "settings_error_automatic": "Could not connect to the Tor controller. The Tor Browser (available from https://www.torproject.org/)\nneeds to be running in the background.", @@ -132,8 +132,8 @@ "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.", "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", "gui_tor_connection_lost": "Disconnected from Tor.", - "gui_server_started_after_timeout": "The set sharing timeframe ran out before the server started.\nPlease make a new share.", - "gui_server_timeout_expired": "The chosen sharing timeframe has already expired.\nPlease update it to start sharing.", + "gui_server_started_after_timeout": "The chosen allowed sharing timeframe ran out before the server started.\nPlease make a new share.", + "gui_server_timeout_expired": "The chosen allowed sharing timeframe already ran out.\nPlease update it to start sharing.", "share_via_onionshare": "OnionShare it", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", -- cgit v1.2.3-54-g00ecf From bead9d93e9a1ac077181872444960b660e2251b9 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Sun, 23 Sep 2018 06:38:39 +0200 Subject: Spelling cancelled ;) --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index 9269611d..bb6b1f37 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "Copy HidServAuth", "gui_downloads": "History of sent shares", "gui_no_downloads": "None yet.", - "gui_canceled": "Canceled", + "gui_canceled": "Cancelled", "gui_copied_url_title": "Copied", "gui_copied_url": "OnionShare address copied to clipboard", "gui_copied_hidservauth_title": "Copied", -- cgit v1.2.3-54-g00ecf From 86f2c198d5a0e667573a850ead64dd4b04c45aea Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 23 Sep 2018 14:00:13 -0700 Subject: Fix some of the language on strings, and move back to "upload" and "download" terminology --- share/locale/en.json | 162 +++++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index bb6b1f37..adec5a20 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -1,32 +1,32 @@ { "config_onion_service": "Setting up onion service on port {0:d}.", - "preparing_files": "Preparing share.", + "preparing_files": "Compressing files.", "give_this_url": "Give this address to the recipient:", - "give_this_url_stealth": "Give this inbox address and HidServAuth line to the recipient:", - "give_this_url_receive": "Give this inbox address to the sender:", - "give_this_url_receive_stealth": "Give this inbox address and HidServAuth to the sender:", + "give_this_url_stealth": "Give this address and HidServAuth line to the recipient:", + "give_this_url_receive": "Give this address to the sender:", + "give_this_url_receive_stealth": "Give this address and HidServAuth to the sender:", "ctrlc_to_stop": "Press Ctrl+C to stop the server", "not_a_file": "{0:s} is not a valid file.", "not_a_readable_file": "{0:s} is not a readable file.", "no_available_port": "Could not find an available port to start the onion service", - "other_page_loaded": "Inbox address loaded", - "close_on_timeout": "Stopped because timeframe ran out", - "closing_automatically": "Stopped because you have the complete share", - "timeout_download_still_running": "Awaiting completion of share transfer to you", + "other_page_loaded": "Address loaded", + "close_on_timeout": "Stopped because auto-stop timer ran out", + "closing_automatically": "Stopped because download finished", + "timeout_download_still_running": "Waiting for download to complete", "large_filesize": "Warning: Sending a large share could take hours", "systray_menu_exit": "Quit", - "systray_download_started_title": "Sending…", - "systray_download_started_message": "Your share is being sent.", - "systray_download_completed_title": "Share sent", - "systray_download_completed_message": "Your share has been sent.", - "systray_download_canceled_title": "Share cancelled", - "systray_download_canceled_message": "The recipient cancelled the share.", - "systray_upload_started_title": "Receiving…", - "systray_upload_started_message": "Started share transfer to inbox.", - "help_local_only": "Avoid using Tor: (Only for development)", - "help_stay_open": "Keep sending after one completion", + "systray_download_started_title": "OnionShare Download Started", + "systray_download_started_message": "A user started downloading your files", + "systray_download_completed_title": "OnionShare Download Finished", + "systray_download_completed_message": "The user finished downloading your files", + "systray_download_canceled_title": "OnionShare Download Canceled", + "systray_download_canceled_message": "The user canceled the download", + "systray_upload_started_title": "OnionShare Upload Started", + "systray_upload_started_message": "A user started uploading files to your computer", + "help_local_only": "Don't use Tor (only for development)", + "help_stay_open": "Keep sharing after first download", "help_shutdown_timeout": "Stop sharing after a given amount of seconds", - "help_stealth": "Make share that requires HidServAuth (advanced)", + "help_stealth": "Use client authorization (advanced)", "help_receive": "Receive shares instead of sending them", "help_debug": "Log OnionShare errors to stdout, and web errors to disk", "help_filename": "List of files or folders to share", @@ -35,40 +35,40 @@ "gui_add": "Add", "gui_delete": "Delete", "gui_choose_items": "Choose", - "gui_share_start_server": "Share", - "gui_share_stop_server": "Stop", - "gui_share_stop_server_shutdown_timeout": "Stop sharing ({}s remaining)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Allowed sharing timeframe ends {}", - "gui_receive_start_server": "Receive", - "gui_receive_stop_server": "Stop", - "gui_receive_stop_server_shutdown_timeout": "Stop receiving ({}s remaining)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "Allowed receive timeframe ends {}", + "gui_share_start_server": "Start sharing", + "gui_share_stop_server": "Stop sharing", + "gui_share_stop_server_shutdown_timeout": "Stop Sharing ({}s remaining)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", + "gui_receive_start_server": "Start Receive Mode", + "gui_receive_stop_server": "Stop Receive Mode", + "gui_receive_stop_server_shutdown_timeout": "Stop Receive Mode ({}s remaining)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "History of sent shares", - "gui_no_downloads": "None yet.", - "gui_canceled": "Cancelled", - "gui_copied_url_title": "Copied", + "gui_downloads": "Download History", + "gui_no_downloads": "No downloads yet.", + "gui_canceled": "Canceled", + "gui_copied_url_title": "Copied OnionShare Address", "gui_copied_url": "OnionShare address copied to clipboard", - "gui_copied_hidservauth_title": "Copied", + "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "HidServAuth line copied to clipboard", "gui_please_wait": "Starting… Click to cancel.", "gui_download_upload_progress_complete": "%p%, {0:s} elapsed.", - "gui_download_upload_progress_starting": "{0:s}, %p% (calculating end time.)", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculating)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Not so fast", - "gui_share_quit_warning": "Quitting now means files en route elsewhere won't finish.", - "gui_receive_quit_warning": "Quitting now means files en route to you won't finish.", + "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", + "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?", "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", - "error_rate_limit": "Too many wrong attempts on your sharing address means someone could be trying to guess it. Start OnionShare again and send a new address to share.", - "zip_progress_bar_format": "Making share smaller: %p%", - "error_stealth_not_supported": "To create shares that require knowing HidServAuth, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.", + "error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the receipient a new address to share.", + "zip_progress_bar_format": "Compressing: %p%", + "error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.", "gui_settings_window_title": "Settings", "gui_settings_whats_this": "What's this?", - "gui_settings_stealth_option": "Create share that requires knowing HidServAuth (legacy)", + "gui_settings_stealth_option": "Use client authorization (legacy)", "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now\nclick to copy your HidServAuth.", "gui_settings_autoupdate_label": "Check for new version", "gui_settings_autoupdate_option": "Notify me when a new version is available", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Check for New Version", "gui_settings_general_label": "General settings", "gui_settings_sharing_label": "Sharing settings", - "gui_settings_close_after_first_download_option": "Stop sharing after first completion", + "gui_settings_close_after_first_download_option": "Stop sharing after first download", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", @@ -87,8 +87,8 @@ "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_socks_label": "SOCKS port", - "gui_settings_authenticate_label": "Tor identification settings", - "gui_settings_authenticate_no_auth_option": "None or cookie based", + "gui_settings_authenticate_label": "Tor authentication settings", + "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", "gui_settings_authenticate_password_option": "Password", "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "Tor bridge support", @@ -104,15 +104,15 @@ "gui_settings_button_save": "Save", "gui_settings_button_cancel": "Cancel", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout_checkbox": "Stop sharing after a given timeframe", - "gui_settings_shutdown_timeout": "Allowed sharing timeframe:", + "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", + "gui_settings_shutdown_timeout": "Stop the share at:", "settings_saved": "Settings saved in {}", - "settings_error_unknown": "Can't connect to Tor controller because it knows your settings aren't making sense.", - "settings_error_automatic": "Could not connect to the Tor controller. The Tor Browser (available from https://www.torproject.org/)\nneeds to be running in the background.", + "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 https://www.torproject.org/)\nrunning in the background?", "settings_error_socket_port": "Can't connect to the Tor controller at {}:{}.", - "settings_error_socket_file": "The Tor controller does not allow connection using the socket file {}.", + "settings_error_socket_file": "Can't connect to the Tor controller using socket file {}.", "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", - "settings_error_missing_password": "Connected to the Tor controller, type in the password for it.", + "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", "settings_error_unreadable_cookie_file": "Connected to the Tor controller, but password may be wrong, or your user is not permitted to read the cookie file.", "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", @@ -125,60 +125,60 @@ "update_available": "New OnionShare out. Click here to get it.

    You are using {} and the latest is {}.", "update_error_check_error": "Could not check for new versions: The OnionShare website is saying the latest version is the unrecognizable '{}'…", "update_error_invalid_latest_version": "Could not check for new version: Maybe you're not connected to Tor, or the OnionShare website is down?", - "update_not_available": "Running the latest OnionShare.", + "update_not_available": "You are running the latest OnionShare.", "gui_tor_connection_ask": "Open the settings to sort out connection to Tor?", "gui_tor_connection_ask_open_settings": "Yes", "gui_tor_connection_ask_quit": "Quit", "gui_tor_connection_error_settings": "Try changing how OnionShare connects to the Tor network in the settings.", "gui_tor_connection_canceled": "Could not connect to Tor.\n\nEnsure you are connected to the Internet, then re-open OnionShare and set up its connection to Tor.", "gui_tor_connection_lost": "Disconnected from Tor.", - "gui_server_started_after_timeout": "The chosen allowed sharing timeframe ran out before the server started.\nPlease make a new share.", - "gui_server_timeout_expired": "The chosen allowed sharing timeframe already ran out.\nPlease update it to start sharing.", + "gui_server_started_after_timeout": "The auto-stop timer ran out before the server started.\nPlease make a new share.", + "gui_server_timeout_expired": "The auto-stop timer already ran out.\nPlease update it to start sharing.", "share_via_onionshare": "OnionShare it", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", - "gui_share_url_description": "Anyone with this OnionShare address can fetch your share using the Tor Browser: ", - "gui_receive_url_description": "Anyone with this link can upload files to your inbox folder using the Tor Browser: ", - "gui_url_label_persistent": "This share will not auto-expire unless a timeframe is set.

    Every subsequent share reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", - "gui_url_label_stay_open": "This share will not auto-expire unless a timeframe is set.", - "gui_url_label_onetime": "This share will expire after first completion.", - "gui_url_label_onetime_and_persistent": "This share will not auto-expire unless a timeframe is set.

    Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", + "gui_share_url_description": "Anyone with this OnionShare address can download your files using the Tor Browser: ", + "gui_receive_url_description": "Anyone with this OnionShare address can upload files to your computer using the Tor Browser: ", + "gui_url_label_persistent": "This share will not auto-stop.

    Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", + "gui_url_label_stay_open": "This share will not auto-stop.", + "gui_url_label_onetime": "This share will stop after first completion.", + "gui_url_label_onetime_and_persistent": "This share will not auto-stop.

    Every subsequent share will reuse the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", "gui_status_indicator_share_stopped": "Ready to share", "gui_status_indicator_share_working": "Starting…", "gui_status_indicator_share_started": "Sharing", - "gui_status_indicator_receive_stopped": "Ready to fetch", + "gui_status_indicator_receive_stopped": "Ready to receive", "gui_status_indicator_receive_working": "Starting…", - "gui_status_indicator_receive_started": "Fetching", + "gui_status_indicator_receive_started": "Receiving", "gui_file_info": "{} files, {}", "gui_file_info_single": "{} file, {}", - "info_in_progress_downloads_tooltip": "{} incoming", - "info_completed_downloads_tooltip": "{} incoming completed", - "info_in_progress_uploads_tooltip": "{} outgoing", - "info_completed_uploads_tooltip": "{} outgoing completed", - "error_cannot_create_downloads_dir": "Could not create inbox: {}", - "error_downloads_dir_not_writable": "The inbox folder is write protected: {}", + "info_in_progress_downloads_tooltip": "{} download(s) in progress", + "info_completed_downloads_tooltip": "{} download(s) completed", + "info_in_progress_uploads_tooltip": "{} upload(s) in progress", + "info_completed_uploads_tooltip": "{} upload(s) completed", + "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", + "error_downloads_dir_not_writable": "The receive mode folder is write protected: {}", "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", - "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer, potentially taking control of it if you open them. Only open things from people you trust to know what they are doing.", - "gui_receive_mode_warning": "A share could contain files that take control of your computer if opened!
    Only open things from people you trust to know what they are doing.", + "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", + "gui_receive_mode_warning": "Receive mode lets someone else upload files to your computer. Some files can potentially take control of your computer if you open them.
    Only open things from people you trust, or if you know what you are doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_received_file": "Received: {}", - "gui_mode_share_button": "Share", - "gui_mode_receive_button": "Receive", - "gui_settings_receiving_label": "Reception settings", - "gui_settings_downloads_label": "Save shares to", + "gui_mode_share_button": "Share Files", + "gui_mode_receive_button": "Receive Files", + "gui_settings_receiving_label": "Receiving settings", + "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Receive mode can be stopped by the sender", "gui_settings_public_mode_checkbox": "Public mode", - "systray_close_server_title": "OnionShare Server Shut Down", + "systray_close_server_title": "OnionShare Server Closed", "systray_close_server_message": "A user closed the server", "systray_page_loaded_title": "OnionShare Page Loaded", - "systray_download_page_loaded_message": "Your page has been loaded", - "systray_upload_page_loaded_message": "Someone is browsing what you share", - "gui_uploads": "Sent Shares", - "gui_no_uploads": "Nothing sent yet.", - "gui_clear_history": "Clear", - "gui_upload_in_progress": "Delivery Started {}", - "gui_upload_finished_range": "Sent {} to {}", - "gui_upload_finished": "Sent {}", - "gui_open_folder_error_nautilus": "Cannot open folder without a file manager. The file is here: {}" + "systray_download_page_loaded_message": "A user loaded the download page", + "systray_upload_page_loaded_message": "A user loaded the upload page", + "gui_uploads": "Upload History", + "gui_no_uploads": "No uploads yet.", + "gui_clear_history": "Clear history", + "gui_upload_in_progress": "Upload Started {}", + "gui_upload_finished_range": "Uploaded {} to {}", + "gui_upload_finished": "Uploaded {}", + "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}" } -- cgit v1.2.3-54-g00ecf From 6193047d6bf239468572dfe72e70294ce785bc0a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 23 Sep 2018 14:36:36 -0700 Subject: Fixes a few strings after testing --- share/locale/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index adec5a20..608fbfbc 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -108,7 +108,7 @@ "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_automatic": "Could not connect to the Tor controller. Is Tor Browser (available from https://www.torproject.org/)\nrunning 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_file": "Can't connect to the Tor controller using socket file {}.", "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", @@ -117,7 +117,7 @@ "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", - "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports shares that require HidServAuth: {}.", + "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.", "error_tor_protocol_error": "There was an error with Tor: {}", "error_tor_protocol_error_unknown": "There was an unknown error with Tor", "error_invalid_private_key": "This private key type is unsupported", @@ -158,8 +158,8 @@ "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", "error_downloads_dir_not_writable": "The receive mode folder is write protected: {}", "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", - "receive_mode_warning": "Warning: Receive mode lets someone else upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", - "gui_receive_mode_warning": "Receive mode lets someone else upload files to your computer. Some files can potentially take control of your computer if you open them.
    Only open things from people you trust, or if you know what you are doing.", + "receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", + "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.

    Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", "receive_mode_received_file": "Received: {}", "gui_mode_share_button": "Share Files", -- cgit v1.2.3-54-g00ecf From e460acbb9135a5a18d5c5d73f9dc36ef1550df22 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 23 Sep 2018 14:39:29 -0700 Subject: Fix locale test --- test/test_onionshare_strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_onionshare_strings.py b/test/test_onionshare_strings.py index 1d0b3206..d3d40c8f 100644 --- a/test/test_onionshare_strings.py +++ b/test/test_onionshare_strings.py @@ -47,7 +47,7 @@ class TestLoadStrings: self, common_obj, locale_en, sys_onionshare_dev_mode): """ load_strings() loads English by default """ strings.load_strings(common_obj) - assert strings._('preparing_files') == "Preparing files to share." + assert strings._('preparing_files') == "Compressing files." def test_load_strings_loads_other_languages( -- cgit v1.2.3-54-g00ecf From de9bc975a4d1420a1bdfd52a90fc0b43fdc16975 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 24 Sep 2018 10:41:48 +1000 Subject: Refactor the unit tests to use common, abstracted tests --- unit_tests/commontests.py | 307 +++++++++++++++++++++ unit_tests/onionshare_receive_mode_upload_test.py | 142 +++------- ...onshare_receive_mode_upload_test_public_mode.py | 144 +++------- unit_tests/onionshare_share_mode_download_test.py | 189 +++---------- ...onshare_share_mode_download_test_public_mode.py | 186 +++---------- ...nionshare_share_mode_download_test_stay_open.py | 171 ++++-------- unit_tests/onionshare_slug_persistent_test.py | 108 ++++---- unit_tests/onionshare_timer_test.py | 86 +++--- 8 files changed, 625 insertions(+), 708 deletions(-) create mode 100644 unit_tests/commontests.py diff --git a/unit_tests/commontests.py b/unit_tests/commontests.py new file mode 100644 index 00000000..1f6f5896 --- /dev/null +++ b/unit_tests/commontests.py @@ -0,0 +1,307 @@ +import os +import requests +import socket +import socks +import zipfile + +from PyQt5 import QtCore, QtTest +from onionshare import strings + +class CommonTests(object): + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_info_widget_is_not_visible(self, mode): + '''Test that the info widget along top of screen is not shown''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.info_widget.isVisible()) + + def test_info_widget_is_visible(self, mode): + '''Test that the info widget along top of screen is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible and that the relevant widget is present''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + if mode == 'share': + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if mode == 'receive': + if not public_mode: + self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + if mode == 'share': + if not public_mode: + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + if mode == 'share': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + if mode == 'receive': + path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) + if mode == 'share': + path = '/{}'.format(self.gui.share_mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.uploads_completed, count) + if mode == 'share': + self.assertEquals(self.gui.share_mode.downloads_completed, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + if stay_open: + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + if mode == 'share': + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + # Auto-stop timer tests + def test_set_timeout(self, mode): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(120) + if mode == 'receive': + self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) + if mode == 'share': + self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + diff --git a/unit_tests/onionshare_receive_mode_upload_test.py b/unit_tests/onionshare_receive_mode_upload_test.py index b99faac0..bac622fb 100644 --- a/unit_tests/onionshare_receive_mode_upload_test.py +++ b/unit_tests/onionshare_receive_mode_upload_test.py @@ -2,20 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -import requests -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -83,176 +81,104 @@ class OnionShareGuiTest(unittest.TestCase): os.remove('/tmp/OnionShare/test-2.txt') @pytest.mark.run(order=1) - def test_gui_loaded_and_tor_bootstrapped(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) def test_info_widget_is_not_visible(self): - '''Test that the info widget along top of screen is not shown because we have a file''' - self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + CommonTests.test_info_widget_is_not_visible(self, 'receive') @pytest.mark.run(order=6) - def test_click_receive_mode(self): - '''Test that we can switch to Receive Mode by clicking the button''' - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') @pytest.mark.run(order=7) - def test_uploads_section_is_visible(self): - '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' - self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') @pytest.mark.run(order=8) def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.receive_mode.server_status.status, 1) + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') @pytest.mark.run(order=9) def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + CommonTests.test_server_status_indicator_says_starting(self, 'receive') @pytest.mark.run(order=10) def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_hidden(self) @pytest.mark.run(order=11) def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.receive_mode.server_status.status, 2) + CommonTests.test_a_server_is_started(self, 'receive') @pytest.mark.run(order=12) def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - # Running in local mode, so we have no .onion - #@pytest.mark.run(order=13) - #def test_have_an_onion_service(self): - # '''Test that we have a valid Onion URL''' - # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - # self.assertEqual(len(self.gui.app.onion_host), 62) + CommonTests.test_a_web_server_is_running(self) @pytest.mark.run(order=14) def test_have_a_slug(self): - '''Test that we have a valid slug''' - self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + CommonTests.test_have_a_slug(self, 'receive', False) @pytest.mark.run(order=15) def test_url_description_shown(self): - '''Test that the URL label is showing''' - self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + CommonTests.test_url_description_shown(self, 'receive') @pytest.mark.run(order=16) def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown''' - self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + CommonTests.test_have_copy_url_button(self, 'receive') @pytest.mark.run(order=17) - def test_server_status_indicator_says_sharing(self): - '''Test that the Server Status indicator shows we are Receiving''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') @pytest.mark.run(order=18) def test_web_page(self): - '''Test that the web page contains the term Select the files you want to send, then click''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.receive_mode.server_status.web.slug) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) - f.close() + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) @pytest.mark.run(order=19) def test_upload_file(self): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') @pytest.mark.run(order=20) - def test_uploads_widget_present(self): - '''Test that the No Uploads Yet label is hidden, that Clear History is present''' - self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') @pytest.mark.run(order=21) - def test_upload_count_incremented(self): - '''Test that the Upload Count has incremented''' - self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) @pytest.mark.run(order=22) def test_upload_same_file_is_renamed(self): - '''Test that we can upload the same file and that it gets renamed''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') @pytest.mark.run(order=23) def test_upload_count_incremented_again(self): - '''Test that the Upload Count has incremented again''' - self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + CommonTests.test_counter_incremented(self, 'receive', 2) @pytest.mark.run(order=24) def test_server_is_stopped(self): - '''Test that the server stops when we click Stop''' - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) + CommonTests.test_server_is_stopped(self, 'receive', False) @pytest.mark.run(order=25) def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + CommonTests.test_web_service_is_stopped(self) @pytest.mark.run(order=26) def test_server_status_indicator_says_closed(self): - '''Test that the Server Status indicator shows we closed''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) if __name__ == "__main__": unittest.main() diff --git a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py index d309e5b1..8ed385f5 100644 --- a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py +++ b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py @@ -2,20 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -import requests -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -83,176 +81,104 @@ class OnionShareGuiTest(unittest.TestCase): os.remove('/tmp/OnionShare/test-2.txt') @pytest.mark.run(order=1) - def test_gui_loaded_and_tor_bootstrapped(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) def test_info_widget_is_not_visible(self): - '''Test that the info widget along top of screen is not shown because we have a file''' - self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + CommonTests.test_info_widget_is_not_visible(self, 'receive') @pytest.mark.run(order=6) - def test_click_receive_mode(self): - '''Test that we can switch to Receive Mode by clicking the button''' - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') @pytest.mark.run(order=7) - def test_uploads_section_is_visible(self): - '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' - self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') @pytest.mark.run(order=8) def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.receive_mode.server_status.status, 1) + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') @pytest.mark.run(order=9) def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + CommonTests.test_server_status_indicator_says_starting(self, 'receive') @pytest.mark.run(order=10) def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_hidden(self) @pytest.mark.run(order=11) def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.receive_mode.server_status.status, 2) + CommonTests.test_a_server_is_started(self, 'receive') @pytest.mark.run(order=12) def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - # Running in local mode, so we have no .onion - #@pytest.mark.run(order=13) - #def test_have_an_onion_service(self): - # '''Test that we have a valid Onion URL''' - # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - # self.assertEqual(len(self.gui.app.onion_host), 62) + CommonTests.test_a_web_server_is_running(self) @pytest.mark.run(order=14) - def test_have_no_slug(self): - '''Test that we have a valid slug''' - self.assertIsNone(self.gui.share_mode.server_status.web.slug) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'receive', True) @pytest.mark.run(order=15) def test_url_description_shown(self): - '''Test that the URL label is showing''' - self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + CommonTests.test_url_description_shown(self, 'receive') @pytest.mark.run(order=16) def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown''' - self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + CommonTests.test_have_copy_url_button(self, 'receive') @pytest.mark.run(order=17) - def test_server_status_indicator_says_sharing(self): - '''Test that the Server Status indicator shows we are Receiving''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') @pytest.mark.run(order=18) def test_web_page(self): - '''Test that the web page contains the term Select the files you want to send, then click''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET / HTTP/1.0\r\n' - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) - f.close() + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) @pytest.mark.run(order=19) def test_upload_file(self): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') @pytest.mark.run(order=20) - def test_uploads_widget_present(self): - '''Test that the No Uploads Yet label is hidden, that Clear History is present''' - self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') @pytest.mark.run(order=21) - def test_upload_count_incremented(self): - '''Test that the Upload Count has incremented''' - self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) @pytest.mark.run(order=22) def test_upload_same_file_is_renamed(self): - '''Test that we can upload the same file and that it gets renamed''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') @pytest.mark.run(order=23) def test_upload_count_incremented_again(self): - '''Test that the Upload Count has incremented again''' - self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + CommonTests.test_counter_incremented(self, 'receive', 2) @pytest.mark.run(order=24) def test_server_is_stopped(self): - '''Test that the server stops when we click Stop''' - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) + CommonTests.test_server_is_stopped(self, 'receive', False) @pytest.mark.run(order=25) def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + CommonTests.test_web_service_is_stopped(self) @pytest.mark.run(order=26) def test_server_status_indicator_says_closed(self): - '''Test that the Server Status indicator shows we closed''' - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) if __name__ == "__main__": unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test.py b/unit_tests/onionshare_share_mode_download_test.py index c4c8bee7..0ef03e97 100644 --- a/unit_tests/onionshare_share_mode_download_test.py +++ b/unit_tests/onionshare_share_mode_download_test.py @@ -2,19 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest, QtGui +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -80,210 +79,112 @@ class OnionShareGuiTest(unittest.TestCase): os.remove('/tmp/test.txt') @pytest.mark.run(order=1) - def test_gui_loaded_and_tor_bootstrapped(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) def test_info_widget_is_visible(self): - '''Test that the info widget along top of screen is shown because we have a file''' - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + CommonTests.test_info_widget_is_visible(self, 'share') @pytest.mark.run(order=7) - def test_downloads_section_is_visible(self): - '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' - self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') @pytest.mark.run(order=8) def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + CommonTests.test_deleting_only_file_hides_delete_button(self) @pytest.mark.run(order=9) def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) @pytest.mark.run(order=10) def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + CommonTests.test_file_selection_widget_readd_files(self) @pytest.mark.run(order=11) def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.share_mode.server_status.status, 1) + CommonTests.test_server_working_on_start_button_pressed(self, 'share') @pytest.mark.run(order=12) def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + CommonTests.test_server_status_indicator_says_starting(self, 'share') @pytest.mark.run(order=13) - def test_add_delete_buttons_now_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) @pytest.mark.run(order=14) def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_hidden(self) @pytest.mark.run(order=15) def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + CommonTests.test_a_server_is_started(self, 'share') @pytest.mark.run(order=16) def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + CommonTests.test_a_web_server_is_running(self) - # Running in local mode, so we have no .onion - #@pytest.mark.run(order=17) - #def test_have_an_onion_service(self): - # '''Test that we have a valid Onion URL''' - # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - # self.assertEqual(len(self.gui.app.onion_host), 62) + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) @pytest.mark.run(order=18) - def test_have_a_slug(self): - '''Test that we have a valid slug''' - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') @pytest.mark.run(order=19) - def test_url_description_shown(self): - '''Test that the URL label is showing''' - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') @pytest.mark.run(order=20) - def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown and can be copied to clipboard''' - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.copy_url_button, QtCore.Qt.LeftButton) - clipboard = self.gui.qtapp.clipboard() - self.assertEquals(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, self.gui.share_mode.server_status.web.slug)) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') @pytest.mark.run(order=21) - def test_server_status_indicator_says_sharing(self): - '''Test that the Server Status indicator shows we are Sharing''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', False) @pytest.mark.run(order=22) - def test_web_page(self): - '''Test that the web page contains the term Total size''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue('Total size' in f.read()) - f.close() + def test_download_share(self): + CommonTests.test_download_share(self, False) @pytest.mark.run(order=23) - def test_download_share(self): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') @pytest.mark.run(order=24) - def test_downloads_widget_present(self): - QtTest.QTest.qWait(1000) - '''Test that the No Downloads Yet label is hidden, that Clear History is present''' - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) - - @pytest.mark.run(order=25) def test_server_is_stopped(self): - '''Test that the server stopped automatically when we downloaded the share''' - self.assertEquals(self.gui.share_mode.server_status.status, 0) + CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=26) + @pytest.mark.run(order=25) def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=27) + @pytest.mark.run(order=26) def test_server_status_indicator_says_closed(self): - '''Test that the Server Status indicator shows we closed because download occurred''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - @pytest.mark.run(order=28) - def test_add_button_visible_again(self): - '''Test that the add button should be visible again''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + @pytest.mark.run(order=27) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) if __name__ == "__main__": diff --git a/unit_tests/onionshare_share_mode_download_test_public_mode.py b/unit_tests/onionshare_share_mode_download_test_public_mode.py index 59905149..417eb8b9 100644 --- a/unit_tests/onionshare_share_mode_download_test_public_mode.py +++ b/unit_tests/onionshare_share_mode_download_test_public_mode.py @@ -2,19 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -80,207 +79,112 @@ class OnionShareGuiTest(unittest.TestCase): os.remove('/tmp/test.txt') @pytest.mark.run(order=1) - def test_gui_loaded_and_tor_bootstrapped(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) def test_info_widget_is_visible(self): - '''Test that the info widget along top of screen is shown because we have a file''' - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + CommonTests.test_info_widget_is_visible(self, 'share') @pytest.mark.run(order=7) - def test_downloads_section_is_visible(self): - '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' - self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') @pytest.mark.run(order=8) def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + CommonTests.test_deleting_only_file_hides_delete_button(self) @pytest.mark.run(order=9) def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) @pytest.mark.run(order=10) def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + CommonTests.test_file_selection_widget_readd_files(self) @pytest.mark.run(order=11) def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.share_mode.server_status.status, 1) + CommonTests.test_server_working_on_start_button_pressed(self, 'share') @pytest.mark.run(order=12) def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + CommonTests.test_server_status_indicator_says_starting(self, 'share') @pytest.mark.run(order=13) - def test_add_delete_buttons_now_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) @pytest.mark.run(order=14) def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_hidden(self) @pytest.mark.run(order=15) def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + CommonTests.test_a_server_is_started(self, 'share') @pytest.mark.run(order=16) def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + CommonTests.test_a_web_server_is_running(self) - # Running in local mode, so we have no .onion - #@pytest.mark.run(order=17) - #def test_have_an_onion_service(self): - # '''Test that we have a valid Onion URL''' - # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - # self.assertEqual(len(self.gui.app.onion_host), 62) + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) @pytest.mark.run(order=18) - def test_have_no_slug(self): - '''Test that we have a valid slug''' - self.assertIsNone(self.gui.share_mode.server_status.web.slug) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') @pytest.mark.run(order=19) - def test_url_description_shown(self): - '''Test that the URL label is showing''' - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') @pytest.mark.run(order=20) - def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown''' - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') @pytest.mark.run(order=21) - def test_server_status_indicator_says_sharing(self): - '''Test that the Server Status indicator shows we are Sharing''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) @pytest.mark.run(order=22) - def test_web_page(self): - '''Test that the web page contains the term Total size''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET / HTTP/1.0\r\n' - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue('Total size' in f.read()) - f.close() + def test_download_share(self): + CommonTests.test_download_share(self, True) @pytest.mark.run(order=23) - def test_download_share(self): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET /download HTTP/1.0\r\n' - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') @pytest.mark.run(order=24) - def test_downloads_widget_present(self): - QtTest.QTest.qWait(1000) - '''Test that the No Downloads Yet label is hidden, that Clear History is present''' - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) - - @pytest.mark.run(order=25) def test_server_is_stopped(self): - '''Test that the server stopped automatically when we downloaded the share''' - self.assertEquals(self.gui.share_mode.server_status.status, 0) + CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=26) + @pytest.mark.run(order=25) def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + CommonTests.test_web_service_is_stopped(self) - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - @pytest.mark.run(order=27) + @pytest.mark.run(order=26) def test_server_status_indicator_says_closed(self): - '''Test that the Server Status indicator shows we closed because download occurred''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - @pytest.mark.run(order=28) - def test_add_button_visible_again(self): - '''Test that the add button should be visible again''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + @pytest.mark.run(order=27) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) if __name__ == "__main__": diff --git a/unit_tests/onionshare_share_mode_download_test_stay_open.py b/unit_tests/onionshare_share_mode_download_test_stay_open.py index 82a0ac87..c3177a38 100644 --- a/unit_tests/onionshare_share_mode_download_test_stay_open.py +++ b/unit_tests/onionshare_share_mode_download_test_stay_open.py @@ -2,19 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -51,7 +50,7 @@ class OnionShareGuiTest(unittest.TestCase): "hidservauth_string": "", "no_bridges": True, "private_key": "", - "public_mode": False, + "public_mode": True, "receive_allow_receiver_shutdown": True, "save_private_key": False, "shutdown_timeout": False, @@ -80,176 +79,124 @@ class OnionShareGuiTest(unittest.TestCase): os.remove('/tmp/test.txt') @pytest.mark.run(order=1) - def test_gui_loaded_and_tor_bootstrapped(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) def test_info_widget_is_visible(self): - '''Test that the info widget along top of screen is shown because we have a file''' - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + CommonTests.test_info_widget_is_visible(self, 'share') @pytest.mark.run(order=7) - def test_downloads_section_is_visible(self): - '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' - self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') @pytest.mark.run(order=8) def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + CommonTests.test_deleting_only_file_hides_delete_button(self) @pytest.mark.run(order=9) def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) @pytest.mark.run(order=10) def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + CommonTests.test_file_selection_widget_readd_files(self) @pytest.mark.run(order=11) def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.share_mode.server_status.status, 1) + CommonTests.test_server_working_on_start_button_pressed(self, 'share') @pytest.mark.run(order=12) def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + CommonTests.test_server_status_indicator_says_starting(self, 'share') @pytest.mark.run(order=13) - def test_add_delete_buttons_now_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) @pytest.mark.run(order=14) def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + CommonTests.test_settings_button_is_hidden(self) @pytest.mark.run(order=15) def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + CommonTests.test_a_server_is_started(self, 'share') @pytest.mark.run(order=16) def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + CommonTests.test_a_web_server_is_running(self) - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - # Running in local mode, so we have no .onion - #@pytest.mark.run(order=17) - #def test_have_an_onion_service(self): - # '''Test that we have a valid Onion URL''' - # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - # self.assertEqual(len(self.gui.app.onion_host), 62) + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) @pytest.mark.run(order=18) - def test_have_a_slug(self): - '''Test that we have a valid slug''' - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') @pytest.mark.run(order=19) - def test_url_description_shown(self): - '''Test that the URL label is showing''' - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') @pytest.mark.run(order=20) - def test_have_copy_url_button(self): - '''Test that the Copy URL button is shown''' - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') @pytest.mark.run(order=21) - def test_server_status_indicator_says_sharing(self): - '''Test that the Server Status indicator shows we are Sharing''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) @pytest.mark.run(order=22) def test_download_share(self): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + CommonTests.test_download_share(self, True) @pytest.mark.run(order=23) - def test_downloads_widget_present(self): - QtTest.QTest.qWait(1000) - '''Test that the No Downloads Yet label is hidden, that Clear History is present''' - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') @pytest.mark.run(order=24) - def test_server_is_not_stopped(self): - '''Test that the server stayed open after we downloaded the share''' - self.assertEquals(self.gui.share_mode.server_status.status, 2) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'share', 1) @pytest.mark.run(order=25) - def test_web_service_is_running(self): - '''Test that the web server is still running''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.assertEquals(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + def test_download_share_again(self): + CommonTests.test_download_share(self, True) @pytest.mark.run(order=26) - def test_download_count_incremented(self): - '''Test that the Download Count has incremented''' - self.assertEquals(self.gui.share_mode.downloads_completed, 1) + def test_counter_incremented_again(self): + CommonTests.test_counter_incremented(self, 'share', 2) + + @pytest.mark.run(order=27) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=28) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=29) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + + @pytest.mark.run(order=30) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) if __name__ == "__main__": diff --git a/unit_tests/onionshare_slug_persistent_test.py b/unit_tests/onionshare_slug_persistent_test.py index d0dcd08a..05c9719a 100644 --- a/unit_tests/onionshare_slug_persistent_test.py +++ b/unit_tests/onionshare_slug_persistent_test.py @@ -2,19 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -83,79 +82,86 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=1) def test_gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) - def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.share_mode.server_status.status, 1) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) - def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) - def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') @pytest.mark.run(order=6) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=8) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=9) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=10) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=11) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=12) def test_have_a_slug(self): - '''Test that we have a valid slug''' - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + CommonTests.test_have_a_slug(self, 'share', False) global slug slug = self.gui.share_mode.server_status.web.slug - @pytest.mark.run(order=7) - def test_server_can_be_stopped(self): - '''Test we can stop the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + @pytest.mark.run(order=13) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') - # Should be in SERVER_STOPPED state - self.assertEqual(self.gui.share_mode.server_status.status, 0) + @pytest.mark.run(order=14) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) - @pytest.mark.run(order=8) + @pytest.mark.run(order=15) def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - QtTest.QTest.qWait(4000) + CommonTests.test_web_service_is_stopped(self) - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + @pytest.mark.run(order=16) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - @pytest.mark.run(order=9) + @pytest.mark.run(order=17) def test_server_started_again(self): - '''Test we can start the service again''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=10) + @pytest.mark.run(order=18) def test_have_same_slug(self): '''Test that we have the same slug''' self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - @pytest.mark.run(order=11) + @pytest.mark.run(order=19) def test_server_is_stopped_again(self): - '''Test that we can stop the server''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(1000) - self.assertEqual(self.gui.share_mode.server_status.status, 0) + CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_web_service_is_stopped(self) + if __name__ == "__main__": unittest.main() diff --git a/unit_tests/onionshare_timer_test.py b/unit_tests/onionshare_timer_test.py index ed20c1c0..34c77f12 100644 --- a/unit_tests/onionshare_timer_test.py +++ b/unit_tests/onionshare_timer_test.py @@ -2,19 +2,18 @@ import os import sys import unittest -import socket import pytest -import zipfile -import socks import json -from PyQt5 import QtCore, QtWidgets, QtTest +from PyQt5 import QtWidgets from onionshare.common import Common from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * +from .commontests import CommonTests + app = QtWidgets.QApplication(sys.argv) class OnionShareGuiTest(unittest.TestCase): @@ -81,62 +80,63 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=1) def test_gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) + CommonTests.test_gui_loaded(self) @pytest.mark.run(order=2) - def test_set_timeout(self): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(120) - self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) @pytest.mark.run(order=3) - def test_server_working_on_start_button_pressed(self): - '''Test we can start the service''' - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - - # Should be in SERVER_WORKING state - self.assertEqual(self.gui.share_mode.server_status.status, 1) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) @pytest.mark.run(order=4) - def test_server_status_indicator_says_starting(self): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) - def test_a_server_is_started(self): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(self.gui.share_mode.server_status.status, 2) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') @pytest.mark.run(order=7) - def test_timeout_widget_hidden(self): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') @pytest.mark.run(order=8) - def test_server_timed_out(self): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(100000) - # We should have timed out now - self.assertEqual(self.gui.share_mode.server_status.status, 0) + def test_set_timeout(self): + CommonTests.test_set_timeout(self, 'share') @pytest.mark.run(order=9) - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=10) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=13) + def test_timeout_widget_hidden(self): + CommonTests.test_timeout_widget_hidden(self, 'share') + + @pytest.mark.run(order=14) + def test_timeout(self): + CommonTests.test_server_timed_out(self, 'share', 120000) + + @pytest.mark.run(order=15) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) if __name__ == "__main__": unittest.main() -- cgit v1.2.3-54-g00ecf From d34364530da881ccc2629d7e05f2a28cc57585f2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 25 Sep 2018 14:40:10 +1000 Subject: Analyse the right file size to determine if the download has finished in the UI (in order to decide whether to stop server yet) --- onionshare/web/share_mode.py | 16 ++++++++-------- onionshare_gui/share_mode/__init__.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index d4d6aed7..a57d0a39 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -71,9 +71,9 @@ class ShareModeWeb(object): # If download is allowed to continue, serve download page if self.should_use_gzip(): - filesize = self.gzip_filesize + self.filesize = self.gzip_filesize else: - filesize = self.download_filesize + self.filesize = self.download_filesize if self.web.slug: r = make_response(render_template( @@ -81,7 +81,7 @@ class ShareModeWeb(object): slug=self.web.slug, file_info=self.file_info, filename=os.path.basename(self.download_filename), - filesize=filesize, + filesize=self.filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) else: @@ -90,7 +90,7 @@ class ShareModeWeb(object): 'send.html', file_info=self.file_info, filename=os.path.basename(self.download_filename), - filesize=filesize, + filesize=self.filesize, filesize_human=self.common.human_readable_filesize(self.download_filesize), is_zipped=self.is_zipped)) return self.web.add_security_headers(r) @@ -132,10 +132,10 @@ class ShareModeWeb(object): use_gzip = self.should_use_gzip() if use_gzip: file_to_download = self.gzip_filename - filesize = self.gzip_filesize + self.filesize = self.gzip_filesize else: file_to_download = self.download_filename - filesize = self.download_filesize + self.filesize = self.download_filesize # Tell GUI the download started self.web.add_request(self.web.REQUEST_STARTED, path, { @@ -175,7 +175,7 @@ class ShareModeWeb(object): # tell GUI the progress downloaded_bytes = fp.tell() - percent = (1.0 * downloaded_bytes / filesize) * 100 + percent = (1.0 * downloaded_bytes / self.filesize) * 100 # only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304) if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -221,7 +221,7 @@ class ShareModeWeb(object): r = Response(generate()) if use_gzip: r.headers.set('Content-Encoding', 'gzip') - r.headers.set('Content-Length', filesize) + r.headers.set('Content-Length', self.filesize) r.headers.set('Content-Disposition', 'attachment', filename=basename) r = self.web.add_security_headers(r) # guess content type diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index ac6a1373..90fce49a 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -246,7 +246,7 @@ class ShareMode(Mode): self.downloads.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? - if event["data"]["bytes"] == self.web.share_mode.download_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)) # Update the total 'completed downloads' info -- cgit v1.2.3-54-g00ecf From 3ed04bf5ec9e6ed13d0d1c676e7da62cfd75f8d5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 25 Sep 2018 15:26:19 +1000 Subject: Show whether Tor version supports next-gen onion support --- onionshare/onion.py | 2 ++ onionshare_gui/settings_dialog.py | 2 +- share/locale/en.json | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 81b82923..c45ae72e 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -402,6 +402,8 @@ class Onion(object): # ephemeral stealth onion services are not supported self.supports_stealth = False + # Does this version of Tor support next-gen ('v3') onions? + self.supports_next_gen_onions = self.tor_version > Version('0.3.3.1') def is_authenticated(self): """ diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index c31d4630..3cd25d31 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -746,7 +746,7 @@ class SettingsDialog(QtWidgets.QDialog): 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 - Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth)) + Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions)) # Clean up onion.cleanup() diff --git a/share/locale/en.json b/share/locale/en.json index 608fbfbc..0f0f0cf4 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -117,7 +117,7 @@ "settings_error_bundled_tor_not_supported": "Using the Tor version that comes with OnionShare does not work in developer mode on Windows or macOS.", "settings_error_bundled_tor_timeout": "Taking too long to connect to Tor. Maybe you aren't connected to the Internet, or have an inaccurate system clock?", "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor in the background:\n{}", - "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.", + "settings_test_success": "Connected to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}.\nSupports client authentication: {}.\nSupports next-gen .onion addresses: {}.", "error_tor_protocol_error": "There was an error with Tor: {}", "error_tor_protocol_error_unknown": "There was an unknown error with Tor", "error_invalid_private_key": "This private key type is unsupported", -- cgit v1.2.3-54-g00ecf From 201f351279ee58166458717415045cfe62c2ac69 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 27 Sep 2018 15:43:59 +1000 Subject: Pass --local-only down to the ServerStatus and Mode so that we can set shorter timeouts for local GUI tests. Update the tests to use a very short timeout --- onionshare_gui/mode.py | 7 +++++-- onionshare_gui/onionshare_gui.py | 4 ++-- onionshare_gui/server_status.py | 26 ++++++++++++++++++-------- unit_tests/commontests.py | 4 ++-- unit_tests/onionshare_timer_test.py | 4 ++-- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 4c21de76..6b156f7e 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -37,7 +37,7 @@ class Mode(QtWidgets.QWidget): starting_server_error = QtCore.pyqtSignal(str) set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None): + def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -54,12 +54,15 @@ class Mode(QtWidgets.QWidget): # The web object gets created in init() self.web = None + # Local mode is passed from OnionShareGui + self.local_only = local_only + # Threads start out as None self.onion_thread = None self.web_thread = None # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app) + self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8b61a18e..83f3a7e0 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -121,7 +121,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setStatusBar(self.status_bar) # Share mode - self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames) + self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only) self.share_mode.init() self.share_mode.server_status.server_started.connect(self.update_server_status_indicator) self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator) @@ -135,7 +135,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.set_server_active.connect(self.set_server_active) # Receive mode - self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray) + self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) self.receive_mode.init() self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator) self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 9afceb38..32135ca4 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -44,7 +44,7 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, file_selection=None): + def __init__(self, common, qtapp, app, file_selection=None, local_only=False): super(ServerStatus, self).__init__() self.common = common @@ -56,17 +56,23 @@ class ServerStatus(QtWidgets.QWidget): self.app = app self.web = None + self.local_only = local_only self.resizeEvent(None) # Shutdown timeout layout self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout = QtWidgets.QDateTimeEdit() - # Set proposed timeout to be 5 minutes into the future self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy") - self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + if self.local_only: + # For testing + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15)) + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime()) + else: + # Set proposed timeout to be 5 minutes into the future + self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) + # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection) shutdown_timeout_layout = QtWidgets.QHBoxLayout() shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) @@ -154,7 +160,8 @@ class ServerStatus(QtWidgets.QWidget): Reset the timeout in the UI after stopping a share """ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300)) - self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120)) + if not self.local_only: + self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60)) def update(self): """ @@ -255,8 +262,11 @@ class ServerStatus(QtWidgets.QWidget): """ if self.status == self.STATUS_STOPPED: if self.common.settings.get('shutdown_timeout'): - # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen - self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) + if self.local_only: + self.timeout = self.shutdown_timeout.dateTime().toPyDateTime() + else: + # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen + self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) diff --git a/unit_tests/commontests.py b/unit_tests/commontests.py index 1f6f5896..de1ad9ab 100644 --- a/unit_tests/commontests.py +++ b/unit_tests/commontests.py @@ -203,9 +203,9 @@ class CommonTests(object): self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) # Auto-stop timer tests - def test_set_timeout(self, mode): + def test_set_timeout(self, mode, timeout): '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(120) + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) if mode == 'receive': self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) diff --git a/unit_tests/onionshare_timer_test.py b/unit_tests/onionshare_timer_test.py index 34c77f12..f36331b8 100644 --- a/unit_tests/onionshare_timer_test.py +++ b/unit_tests/onionshare_timer_test.py @@ -108,7 +108,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=8) def test_set_timeout(self): - CommonTests.test_set_timeout(self, 'share') + CommonTests.test_set_timeout(self, 'share', 5) @pytest.mark.run(order=9) def test_server_working_on_start_button_pressed(self): @@ -132,7 +132,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=14) def test_timeout(self): - CommonTests.test_server_timed_out(self, 'share', 120000) + CommonTests.test_server_timed_out(self, 'share', 10000) @pytest.mark.run(order=15) def test_web_service_is_stopped(self): -- cgit v1.2.3-54-g00ecf From 8fc8e0765c73530136b9cd203bc059be09bd8475 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 27 Sep 2018 15:54:46 +1000 Subject: Rename test dir to tests. Rename unit_tests to tests_gui_local. Add test dependencies. Update various paths. Add GUI unit tests docs to BUILD.md --- .travis.yml | 2 +- BUILD.md | 17 +- MANIFEST.in | 2 +- install/check_lacked_trans.py | 2 +- install/requirements-tests.txt | 11 + test/__init__.py | 0 test/conftest.py | 160 ----------- test/test_helpers.py | 38 --- test/test_onionshare.py | 86 ------ test/test_onionshare_common.py | 281 ------------------- test/test_onionshare_settings.py | 178 ------------ test/test_onionshare_strings.py | 63 ----- test/test_onionshare_web.py | 255 ----------------- tests/__init__.py | 0 tests/conftest.py | 160 +++++++++++ tests/test_helpers.py | 38 +++ tests/test_onionshare.py | 86 ++++++ tests/test_onionshare_common.py | 281 +++++++++++++++++++ tests/test_onionshare_settings.py | 178 ++++++++++++ tests/test_onionshare_strings.py | 63 +++++ tests/test_onionshare_web.py | 255 +++++++++++++++++ tests_gui_local/__init__.py | 0 tests_gui_local/commontests.py | 307 +++++++++++++++++++++ tests_gui_local/conftest.py | 160 +++++++++++ .../onionshare_receive_mode_upload_test.py | 184 ++++++++++++ ...onshare_receive_mode_upload_test_public_mode.py | 184 ++++++++++++ .../onionshare_share_mode_download_test.py | 191 +++++++++++++ ...onshare_share_mode_download_test_public_mode.py | 191 +++++++++++++ ...nionshare_share_mode_download_test_stay_open.py | 203 ++++++++++++++ tests_gui_local/onionshare_slug_persistent_test.py | 167 +++++++++++ tests_gui_local/onionshare_timer_test.py | 142 ++++++++++ tests_gui_local/run_unit_tests.sh | 5 + unit_tests/__init__.py | 0 unit_tests/commontests.py | 307 --------------------- unit_tests/conftest.py | 160 ----------- unit_tests/onionshare_receive_mode_upload_test.py | 184 ------------ ...onshare_receive_mode_upload_test_public_mode.py | 184 ------------ unit_tests/onionshare_share_mode_download_test.py | 191 ------------- ...onshare_share_mode_download_test_public_mode.py | 191 ------------- ...nionshare_share_mode_download_test_stay_open.py | 203 -------------- unit_tests/onionshare_slug_persistent_test.py | 167 ----------- unit_tests/onionshare_timer_test.py | 142 ---------- unit_tests/run_unit_tests.sh | 5 - 43 files changed, 2824 insertions(+), 2800 deletions(-) create mode 100644 install/requirements-tests.txt delete mode 100644 test/__init__.py delete mode 100644 test/conftest.py delete mode 100644 test/test_helpers.py delete mode 100644 test/test_onionshare.py delete mode 100644 test/test_onionshare_common.py delete mode 100644 test/test_onionshare_settings.py delete mode 100644 test/test_onionshare_strings.py delete mode 100644 test/test_onionshare_web.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_helpers.py create mode 100644 tests/test_onionshare.py create mode 100644 tests/test_onionshare_common.py create mode 100644 tests/test_onionshare_settings.py create mode 100644 tests/test_onionshare_strings.py create mode 100644 tests/test_onionshare_web.py create mode 100644 tests_gui_local/__init__.py create mode 100644 tests_gui_local/commontests.py create mode 100644 tests_gui_local/conftest.py create mode 100644 tests_gui_local/onionshare_receive_mode_upload_test.py create mode 100644 tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test_public_mode.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test_stay_open.py create mode 100644 tests_gui_local/onionshare_slug_persistent_test.py create mode 100644 tests_gui_local/onionshare_timer_test.py create mode 100755 tests_gui_local/run_unit_tests.sh delete mode 100644 unit_tests/__init__.py delete mode 100644 unit_tests/commontests.py delete mode 100644 unit_tests/conftest.py delete mode 100644 unit_tests/onionshare_receive_mode_upload_test.py delete mode 100644 unit_tests/onionshare_receive_mode_upload_test_public_mode.py delete mode 100644 unit_tests/onionshare_share_mode_download_test.py delete mode 100644 unit_tests/onionshare_share_mode_download_test_public_mode.py delete mode 100644 unit_tests/onionshare_share_mode_download_test_stay_open.py delete mode 100644 unit_tests/onionshare_slug_persistent_test.py delete mode 100644 unit_tests/onionshare_timer_test.py delete mode 100755 unit_tests/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index afbaa887..a41339cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,6 @@ before_script: # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests -script: pytest --cov=onionshare test/ +script: pytest --cov=onionshare tests/ after_success: - coveralls diff --git a/BUILD.md b/BUILD.md index 1feedf49..308a186c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -137,8 +137,21 @@ This will prompt you to codesign three binaries and execute one unsigned binary. ## Tests -OnionShare includes PyTest unit tests. To run the tests: +OnionShare includes PyTest unit tests. To run the tests, first install some dependencies: ```sh -pytest test/ +pip3 install -r install/requirements-tests.txt +``` + +If you'd like to run the CLI-based tests that Travis runs: + +```sh +pytest tests/ +``` + +If you would like to run the GUI unit tests in 'local only mode': + +```sh +cd tests_gui_local/ +./run_unit_tests.sh ``` diff --git a/MANIFEST.in b/MANIFEST.in index c8a4d87c..71af3740 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,4 +10,4 @@ include install/onionshare.desktop include install/onionshare.appdata.xml include install/onionshare80.xpm include install/scripts/onionshare-nautilus.py -include test/*.py +include tests/*.py diff --git a/install/check_lacked_trans.py b/install/check_lacked_trans.py index 027edab1..1caa6b27 100644 --- a/install/check_lacked_trans.py +++ b/install/check_lacked_trans.py @@ -59,7 +59,7 @@ def main(): files_in(dir, 'onionshare_gui/share_mode') + \ files_in(dir, 'onionshare_gui/receive_mode') + \ files_in(dir, 'install/scripts') + \ - files_in(dir, 'test') + files_in(dir, 'tests') pysrc = [p for p in src if p.endswith('.py')] lang_code = args.lang_code diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt new file mode 100644 index 00000000..0d9c1581 --- /dev/null +++ b/install/requirements-tests.txt @@ -0,0 +1,11 @@ +atomicwrites==1.2.1 +attrs==18.2.0 +more-itertools==4.3.0 +pluggy==0.6.0 +py==1.6.0 +pytest==3.4.2 +pytest-faulthandler==1.5.0 +pytest-ordering==0.5 +pytest-qt==3.1.0 +six==1.11.0 +urllib3==1.23 diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index 8ac7efb8..00000000 --- a/test/conftest.py +++ /dev/null @@ -1,160 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - return common.Common() - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/test/test_helpers.py b/test/test_helpers.py deleted file mode 100644 index 321afbb7..00000000 --- a/test/test_helpers.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import tempfile -import os - - -class MockSubprocess(): - def __init__(self): - self.last_call = None - - def call(self, args): - self.last_call = args - - def last_call_args(self): - return self.last_call - - -def write_tempfile(text): - path = os.path.join(tempfile.mkdtemp(), "/test-file.txt") - with open(path, "w") as f: - f.write(text) - return path diff --git a/test/test_onionshare.py b/test/test_onionshare.py deleted file mode 100644 index 7592a777..00000000 --- a/test/test_onionshare.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import os - -import pytest - -from onionshare import OnionShare -from onionshare.common import Common - - -class MyOnion: - def __init__(self, stealth=False): - self.auth_string = 'TestHidServAuth' - self.private_key = '' - self.stealth = stealth - - @staticmethod - def start_onion_service(_): - return 'test_service_id.onion' - - -@pytest.fixture -def onionshare_obj(): - common = Common() - return OnionShare(common, MyOnion()) - - -class TestOnionShare: - def test_init(self, onionshare_obj): - assert onionshare_obj.hidserv_dir is None - assert onionshare_obj.onion_host is None - assert onionshare_obj.stealth is None - assert onionshare_obj.cleanup_filenames == [] - assert onionshare_obj.local_only is False - - def test_set_stealth_true(self, onionshare_obj): - onionshare_obj.set_stealth(True) - assert onionshare_obj.stealth is True - assert onionshare_obj.onion.stealth is True - - def test_set_stealth_false(self, onionshare_obj): - onionshare_obj.set_stealth(False) - assert onionshare_obj.stealth is False - assert onionshare_obj.onion.stealth is False - - def test_start_onion_service(self, onionshare_obj): - onionshare_obj.set_stealth(False) - onionshare_obj.start_onion_service() - assert 17600 <= onionshare_obj.port <= 17650 - assert onionshare_obj.onion_host == 'test_service_id.onion' - - def test_start_onion_service_stealth(self, onionshare_obj): - onionshare_obj.set_stealth(True) - onionshare_obj.start_onion_service() - assert onionshare_obj.auth_string == 'TestHidServAuth' - - def test_start_onion_service_local_only(self, onionshare_obj): - onionshare_obj.local_only = True - onionshare_obj.start_onion_service() - assert onionshare_obj.onion_host == '127.0.0.1:{}'.format( - onionshare_obj.port) - - def test_cleanup(self, onionshare_obj, temp_dir_1024, temp_file_1024): - onionshare_obj.cleanup_filenames = [temp_dir_1024, temp_file_1024] - onionshare_obj.cleanup() - - assert os.path.exists(temp_dir_1024) is False - assert os.path.exists(temp_dir_1024) is False - assert onionshare_obj.cleanup_filenames == [] diff --git a/test/test_onionshare_common.py b/test/test_onionshare_common.py deleted file mode 100644 index d70f2c0e..00000000 --- a/test/test_onionshare_common.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import contextlib -import inspect -import io -import os -import random -import re -import socket -import sys -import zipfile - -import pytest - -LOG_MSG_REGEX = re.compile(r""" - ^\[Jun\ 06\ 2013\ 11:05:00\] - \ TestModule\.\.dummy_func - \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE) -SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') - - -# TODO: Improve the Common tests to test it all as a single class - - -class TestBuildSlug: - @pytest.mark.parametrize('test_input,expected', ( - # VALID, two lowercase words, separated by a hyphen - ('syrup-enzyme', True), - ('caution-friday', True), - - # VALID, two lowercase words, with one hyphenated compound word - ('drop-down-thimble', True), - ('unmixed-yo-yo', True), - - # VALID, two lowercase hyphenated compound words, separated by hyphen - ('yo-yo-drop-down', True), - ('felt-tip-t-shirt', True), - ('hello-world', True), - - # INVALID - ('Upper-Case', False), - ('digits-123', False), - ('too-many-hyphens-', False), - ('symbols-!@#$%', False) - )) - def test_build_slug_regex(self, test_input, expected): - """ Test that `SLUG_REGEX` accounts for the following patterns - - There are a few hyphenated words in `wordlist.txt`: - * drop-down - * felt-tip - * t-shirt - * yo-yo - - These words cause a few extra potential slug patterns: - * word-word - * hyphenated-word-word - * word-hyphenated-word - * hyphenated-word-hyphenated-word - """ - - assert bool(SLUG_REGEX.match(test_input)) == expected - - def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode): - assert common_obj.build_slug() != common_obj.build_slug() - - -class TestDirSize: - def test_temp_dir_size(self, common_obj, temp_dir_1024_delete): - """ dir_size() should return the total size (in bytes) of all files - in a particular directory. - """ - - assert common_obj.dir_size(temp_dir_1024_delete) == 1024 - - -class TestEstimatedTimeRemaining: - @pytest.mark.parametrize('test_input,expected', ( - ((2, 676, 12), '8h14m16s'), - ((14, 1049, 30), '1h26m15s'), - ((21, 450, 1), '33m42s'), - ((31, 1115, 80), '11m39s'), - ((336, 989, 32), '2m12s'), - ((603, 949, 38), '36s'), - ((971, 1009, 83), '1s') - )) - def test_estimated_time_remaining( - self, common_obj, test_input, expected, time_time_100): - assert common_obj.estimated_time_remaining(*test_input) == expected - - @pytest.mark.parametrize('test_input', ( - (10, 20, 100), # if `time_elapsed == 0` - (0, 37, 99) # if `download_rate == 0` - )) - def test_raises_zero_division_error(self, common_obj, test_input, time_time_100): - with pytest.raises(ZeroDivisionError): - common_obj.estimated_time_remaining(*test_input) - - -class TestFormatSeconds: - @pytest.mark.parametrize('test_input,expected', ( - (0, '0s'), - (26, '26s'), - (60, '1m'), - (947.35, '15m47s'), - (1847, '30m47s'), - (2193.94, '36m34s'), - (3600, '1h'), - (13426.83, '3h43m47s'), - (16293, '4h31m33s'), - (18392.14, '5h6m32s'), - (86400, '1d'), - (129674, '1d12h1m14s'), - (56404.12, '15h40m4s') - )) - def test_format_seconds(self, common_obj, test_input, expected): - assert common_obj.format_seconds(test_input) == expected - - # TODO: test negative numbers? - @pytest.mark.parametrize('test_input', ( - 'string', lambda: None, [], {}, set() - )) - def test_invalid_input_types(self, common_obj, test_input): - with pytest.raises(TypeError): - common_obj.format_seconds(test_input) - - -class TestGetAvailablePort: - @pytest.mark.parametrize('port_min,port_max', ( - (random.randint(1024, 1500), - random.randint(1800, 2048)) for _ in range(50) - )) - def test_returns_an_open_port(self, common_obj, port_min, port_max): - """ get_available_port() should return an open port within the range """ - - port = common_obj.get_available_port(port_min, port_max) - assert port_min <= port <= port_max - with socket.socket() as tmpsock: - tmpsock.bind(('127.0.0.1', port)) - - -class TestGetPlatform: - def test_darwin(self, platform_darwin, common_obj): - assert common_obj.platform == 'Darwin' - - def test_linux(self, platform_linux, common_obj): - assert common_obj.platform == 'Linux' - - def test_windows(self, platform_windows, common_obj): - assert common_obj.platform == 'Windows' - - -# TODO: double-check these tests -class TestGetResourcePath: - def test_onionshare_dev_mode(self, common_obj, sys_onionshare_dev_mode): - prefix = os.path.join( - os.path.dirname( - os.path.dirname( - os.path.abspath( - inspect.getfile( - inspect.currentframe())))), 'share') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) - - def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix): - prefix = os.path.join(sys.prefix, 'share/onionshare') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) - - def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass): - prefix = os.path.join(sys._MEIPASS, 'share') - assert ( - common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == - os.path.join(prefix, 'test_filename')) - - -class TestGetTorPaths: - # @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ? - def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass): - base_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - common_obj.get_resource_path('')))) - tor_path = os.path.join( - base_path, 'Resources', 'Tor', 'tor') - tor_geo_ip_file_path = os.path.join( - base_path, 'Resources', 'Tor', 'geoip') - tor_geo_ipv6_file_path = os.path.join( - base_path, 'Resources', 'Tor', 'geoip6') - obfs4proxy_file_path = os.path.join( - base_path, 'Resources', 'Tor', 'obfs4proxy') - assert (common_obj.get_tor_paths() == - (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) - - # @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ? - def test_get_tor_paths_linux(self, platform_linux, common_obj): - assert (common_obj.get_tor_paths() == - ('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy')) - - # @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ? - def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen): - base_path = os.path.join( - os.path.dirname( - os.path.dirname( - common_obj.get_resource_path(''))), 'tor') - tor_path = os.path.join( - os.path.join(base_path, 'Tor'), 'tor.exe') - obfs4proxy_file_path = os.path.join( - os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') - tor_geo_ip_file_path = os.path.join( - os.path.join( - os.path.join(base_path, 'Data'), 'Tor'), 'geoip') - tor_geo_ipv6_file_path = os.path.join( - os.path.join( - os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') - assert (common_obj.get_tor_paths() == - (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) - - -class TestHumanReadableFilesize: - @pytest.mark.parametrize('test_input,expected', ( - (1024 ** 0, '1.0 B'), - (1024 ** 1, '1.0 KiB'), - (1024 ** 2, '1.0 MiB'), - (1024 ** 3, '1.0 GiB'), - (1024 ** 4, '1.0 TiB'), - (1024 ** 5, '1.0 PiB'), - (1024 ** 6, '1.0 EiB'), - (1024 ** 7, '1.0 ZiB'), - (1024 ** 8, '1.0 YiB') - )) - def test_human_readable_filesize(self, common_obj, test_input, expected): - assert common_obj.human_readable_filesize(test_input) == expected - - -class TestLog: - @pytest.mark.parametrize('test_input', ( - ('[Jun 06 2013 11:05:00]' - ' TestModule..dummy_func' - ' at 0xdeadbeef>'), - ('[Jun 06 2013 11:05:00]' - ' TestModule..dummy_func' - ' at 0xdeadbeef>: TEST_MSG') - )) - def test_log_msg_regex(self, test_input): - assert bool(LOG_MSG_REGEX.match(test_input)) - - def test_output(self, common_obj, time_strftime): - def dummy_func(): - pass - - common_obj.debug = True - - # From: https://stackoverflow.com/questions/1218933 - with io.StringIO() as buf, contextlib.redirect_stdout(buf): - common_obj.log('TestModule', dummy_func) - common_obj.log('TestModule', dummy_func, 'TEST_MSG') - output = buf.getvalue() - - line_one, line_two, _ = output.split('\n') - assert LOG_MSG_REGEX.match(line_one) - assert LOG_MSG_REGEX.match(line_two) diff --git a/test/test_onionshare_settings.py b/test/test_onionshare_settings.py deleted file mode 100644 index 1f1ef528..00000000 --- a/test/test_onionshare_settings.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import json -import os -import tempfile - -import pytest - -from onionshare import common, settings, strings - - -@pytest.fixture -def os_path_expanduser(monkeypatch): - monkeypatch.setattr('os.path.expanduser', lambda path: path) - - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) - - -class TestSettings: - def test_init(self, settings_obj): - assert settings_obj._settings == settings_obj.default_settings == { - 'version': 'DUMMY_VERSION_1.2.3', - 'connection_type': 'bundled', - 'control_port_address': '127.0.0.1', - 'control_port_port': 9051, - 'socks_address': '127.0.0.1', - 'socks_port': 9050, - 'socket_file_path': '/var/run/tor/control', - 'auth_type': 'no_auth', - 'auth_password': '', - 'close_after_first_download': True, - 'shutdown_timeout': False, - 'use_stealth': False, - 'use_autoupdate': True, - 'autoupdate_timestamp': None, - 'no_bridges': True, - 'tor_bridges_use_obfs4': False, - 'tor_bridges_use_meek_lite_azure': False, - 'tor_bridges_use_custom_bridges': '', - 'use_legacy_v2_onions': False, - 'save_private_key': False, - 'private_key': '', - 'slug': '', - 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/OnionShare'), - 'receive_allow_receiver_shutdown': True, - 'public_mode': False - } - - def test_fill_in_defaults(self, settings_obj): - del settings_obj._settings['version'] - settings_obj.fill_in_defaults() - assert settings_obj._settings['version'] == 'DUMMY_VERSION_1.2.3' - - def test_load(self, settings_obj): - custom_settings = { - 'version': 'CUSTOM_VERSION', - 'socks_port': 9999, - 'use_stealth': True - } - tmp_file, tmp_file_path = tempfile.mkstemp() - with open(tmp_file, 'w') as f: - json.dump(custom_settings, f) - settings_obj.filename = tmp_file_path - settings_obj.load() - - assert settings_obj._settings['version'] == 'CUSTOM_VERSION' - assert settings_obj._settings['socks_port'] == 9999 - assert settings_obj._settings['use_stealth'] is True - - os.remove(tmp_file_path) - assert os.path.exists(tmp_file_path) is False - - def test_save(self, monkeypatch, settings_obj): - monkeypatch.setattr(strings, '_', lambda _: '') - - settings_filename = 'default_settings.json' - tmp_dir = tempfile.gettempdir() - settings_path = os.path.join(tmp_dir, settings_filename) - settings_obj.filename = settings_path - settings_obj.save() - with open(settings_path, 'r') as f: - settings = json.load(f) - - assert settings_obj._settings == settings - - os.remove(settings_path) - assert os.path.exists(settings_path) is False - - def test_get(self, settings_obj): - assert settings_obj.get('version') == 'DUMMY_VERSION_1.2.3' - assert settings_obj.get('connection_type') == 'bundled' - assert settings_obj.get('control_port_address') == '127.0.0.1' - assert settings_obj.get('control_port_port') == 9051 - assert settings_obj.get('socks_address') == '127.0.0.1' - assert settings_obj.get('socks_port') == 9050 - assert settings_obj.get('socket_file_path') == '/var/run/tor/control' - assert settings_obj.get('auth_type') == 'no_auth' - assert settings_obj.get('auth_password') == '' - assert settings_obj.get('close_after_first_download') is True - assert settings_obj.get('use_stealth') is False - assert settings_obj.get('use_autoupdate') is True - assert settings_obj.get('autoupdate_timestamp') is None - assert settings_obj.get('autoupdate_timestamp') is None - assert settings_obj.get('no_bridges') is True - assert settings_obj.get('tor_bridges_use_obfs4') is False - assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False - assert settings_obj.get('tor_bridges_use_custom_bridges') == '' - - - def test_set_version(self, settings_obj): - settings_obj.set('version', 'CUSTOM_VERSION') - assert settings_obj._settings['version'] == 'CUSTOM_VERSION' - - def test_set_control_port_port(self, settings_obj): - settings_obj.set('control_port_port', 999) - assert settings_obj._settings['control_port_port'] == 999 - - settings_obj.set('control_port_port', 'NON_INTEGER') - assert settings_obj._settings['control_port_port'] == 9051 - - def test_set_socks_port(self, settings_obj): - settings_obj.set('socks_port', 888) - assert settings_obj._settings['socks_port'] == 888 - - settings_obj.set('socks_port', 'NON_INTEGER') - assert settings_obj._settings['socks_port'] == 9050 - - def test_filename_darwin( - self, - monkeypatch, - os_path_expanduser, - platform_darwin): - obj = settings.Settings(common.Common()) - assert (obj.filename == - '~/Library/Application Support/OnionShare/onionshare.json') - - def test_filename_linux( - self, - monkeypatch, - os_path_expanduser, - platform_linux): - obj = settings.Settings(common.Common()) - assert obj.filename == '~/.config/onionshare/onionshare.json' - - def test_filename_windows( - self, - monkeypatch, - platform_windows): - monkeypatch.setenv('APPDATA', 'C:') - obj = settings.Settings(common.Common()) - assert obj.filename == 'C:\\OnionShare\\onionshare.json' - - def test_set_custom_bridge(self, settings_obj): - settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E') - assert settings_obj._settings['tor_bridges_use_custom_bridges'] == 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E' diff --git a/test/test_onionshare_strings.py b/test/test_onionshare_strings.py deleted file mode 100644 index d3d40c8f..00000000 --- a/test/test_onionshare_strings.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import types - -import pytest - -from onionshare import strings - - -# # Stub get_resource_path so it finds the correct path while running tests -# def get_resource_path(filename): -# resources_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'share') -# path = os.path.join(resources_dir, filename) -# return 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(): - assert callable(strings._) and isinstance(strings._, types.FunctionType) - - -class TestLoadStrings: - def test_load_strings_defaults_to_english( - self, common_obj, locale_en, sys_onionshare_dev_mode): - """ load_strings() loads English by default """ - strings.load_strings(common_obj) - assert strings._('preparing_files') == "Compressing files." - - - def test_load_strings_loads_other_languages( - self, common_obj, locale_fr, sys_onionshare_dev_mode): - """ load_strings() loads other languages in different locales """ - strings.load_strings(common_obj, "fr") - assert strings._('preparing_files') == "Préparation des fichiers à partager." - - def test_load_invalid_locale( - self, common_obj, locale_invalid, sys_onionshare_dev_mode): - """ load_strings() raises a KeyError for an invalid locale """ - with pytest.raises(KeyError): - strings.load_strings(common_obj, 'XX') diff --git a/test/test_onionshare_web.py b/test/test_onionshare_web.py deleted file mode 100644 index 24a0e163..00000000 --- a/test/test_onionshare_web.py +++ /dev/null @@ -1,255 +0,0 @@ -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" - -import contextlib -import inspect -import io -import os -import random -import re -import socket -import sys -import zipfile -import tempfile - -import pytest - -from onionshare.common import Common -from onionshare.web import Web -from onionshare.settings import Settings - -DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') -RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') - - -def web_obj(common_obj, mode, num_files=0): - """ Creates a Web object, in either share mode or receive mode, ready for testing """ - common_obj.load_settings() - - web = Web(common_obj, False, mode) - web.generate_slug() - web.stay_open = True - web.running = True - - web.app.testing = True - - # Share mode - if mode == 'share': - # Add files - files = [] - for i in range(num_files): - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - files.append(tmp_file.name) - web.share_mode.set_file_info(files) - # Receive mode - else: - pass - - return web - - -class TestWeb: - def test_share_mode(self, common_obj): - web = web_obj(common_obj, 'share', 3) - assert web.mode is 'share' - with web.app.test_client() as c: - # Load 404 pages - res = c.get('/') - res.get_data() - assert res.status_code == 404 - - res = c.get('/invalidslug'.format(web.slug)) - res.get_data() - assert res.status_code == 404 - - # Load download page - res = c.get('/{}'.format(web.slug)) - res.get_data() - assert res.status_code == 200 - - # Download - res = c.get('/{}/download'.format(web.slug)) - res.get_data() - assert res.status_code == 200 - assert res.mimetype == 'application/zip' - - def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024): - web = web_obj(common_obj, 'share', 3) - web.stay_open = False - - assert web.running == True - - with web.app.test_client() as c: - # Download the first time - res = c.get('/{}/download'.format(web.slug)) - res.get_data() - assert res.status_code == 200 - assert res.mimetype == 'application/zip' - - assert web.running == False - - def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024): - web = web_obj(common_obj, 'share', 3) - web.stay_open = True - - assert web.running == True - - with web.app.test_client() as c: - # Download the first time - res = c.get('/{}/download'.format(web.slug)) - res.get_data() - assert res.status_code == 200 - assert res.mimetype == 'application/zip' - assert web.running == True - - def test_receive_mode(self, common_obj): - web = web_obj(common_obj, 'receive') - assert web.mode is 'receive' - - with web.app.test_client() as c: - # Load 404 pages - res = c.get('/') - res.get_data() - assert res.status_code == 404 - - res = c.get('/invalidslug'.format(web.slug)) - res.get_data() - assert res.status_code == 404 - - # Load upload page - res = c.get('/{}'.format(web.slug)) - res.get_data() - assert res.status_code == 200 - - def test_receive_mode_allow_receiver_shutdown_on(self, common_obj): - web = web_obj(common_obj, 'receive') - - common_obj.settings.set('receive_allow_receiver_shutdown', True) - - assert web.running == True - - with web.app.test_client() as c: - # Load close page - res = c.post('/{}/close'.format(web.slug)) - res.get_data() - # Should return ok, and server should stop - assert res.status_code == 200 - assert web.running == False - - def test_receive_mode_allow_receiver_shutdown_off(self, common_obj): - web = web_obj(common_obj, 'receive') - - common_obj.settings.set('receive_allow_receiver_shutdown', False) - - assert web.running == True - - with web.app.test_client() as c: - # Load close page - res = c.post('/{}/close'.format(web.slug)) - res.get_data() - # Should redirect to index, and server should still be running - assert res.status_code == 302 - assert web.running == True - - def test_public_mode_on(self, common_obj): - web = web_obj(common_obj, 'receive') - common_obj.settings.set('public_mode', True) - - with web.app.test_client() as c: - # Upload page should be accessible from / - res = c.get('/') - data1 = res.get_data() - assert res.status_code == 200 - - # /[slug] should be a 404 - res = c.get('/{}'.format(web.slug)) - data2 = res.get_data() - assert res.status_code == 404 - - def test_public_mode_off(self, common_obj): - web = web_obj(common_obj, 'receive') - common_obj.settings.set('public_mode', False) - - with web.app.test_client() as c: - # / should be a 404 - res = c.get('/') - data1 = res.get_data() - assert res.status_code == 404 - - # Upload page should be accessible from /[slug] - res = c.get('/{}'.format(web.slug)) - data2 = res.get_data() - assert res.status_code == 200 - - -class TestZipWriterDefault: - @pytest.mark.parametrize('test_input', ( - 'onionshare_{}.zip'.format(''.join( - random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6) - )) for _ in range(50) - )) - def test_default_zw_filename_regex(self, test_input): - assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input)) - - def test_zw_filename(self, default_zw): - zw_filename = os.path.basename(default_zw.zip_filename) - assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename)) - - def test_zipfile_filename_matches_zipwriter_filename(self, default_zw): - assert default_zw.z.filename == default_zw.zip_filename - - def test_zipfile_allow_zip64(self, default_zw): - assert default_zw.z._allowZip64 is True - - def test_zipfile_mode(self, default_zw): - assert default_zw.z.mode == 'w' - - def test_callback(self, default_zw): - assert default_zw.processed_size_callback(None) is None - - def test_add_file(self, default_zw, temp_file_1024_delete): - default_zw.add_file(temp_file_1024_delete) - zipfile_info = default_zw.z.getinfo( - os.path.basename(temp_file_1024_delete)) - - assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED - assert zipfile_info.file_size == 1024 - - def test_add_directory(self, temp_dir_1024_delete, default_zw): - previous_size = default_zw._size # size before adding directory - default_zw.add_dir(temp_dir_1024_delete) - assert default_zw._size == previous_size + 1024 - - -class TestZipWriterCustom: - @pytest.mark.parametrize('test_input', ( - Common.random_string( - random.randint(2, 50), - random.choice((None, random.randint(2, 50))) - ) for _ in range(50) - )) - def test_random_string_regex(self, test_input): - assert bool(RANDOM_STR_REGEX.match(test_input)) - - def test_custom_filename(self, custom_zw): - assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename)) - - def test_custom_callback(self, custom_zw): - assert custom_zw.processed_size_callback(None) == 'custom_callback' diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..321afbb7 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,38 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import tempfile +import os + + +class MockSubprocess(): + def __init__(self): + self.last_call = None + + def call(self, args): + self.last_call = args + + def last_call_args(self): + return self.last_call + + +def write_tempfile(text): + path = os.path.join(tempfile.mkdtemp(), "/test-file.txt") + with open(path, "w") as f: + f.write(text) + return path diff --git a/tests/test_onionshare.py b/tests/test_onionshare.py new file mode 100644 index 00000000..7592a777 --- /dev/null +++ b/tests/test_onionshare.py @@ -0,0 +1,86 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import os + +import pytest + +from onionshare import OnionShare +from onionshare.common import Common + + +class MyOnion: + def __init__(self, stealth=False): + self.auth_string = 'TestHidServAuth' + self.private_key = '' + self.stealth = stealth + + @staticmethod + def start_onion_service(_): + return 'test_service_id.onion' + + +@pytest.fixture +def onionshare_obj(): + common = Common() + return OnionShare(common, MyOnion()) + + +class TestOnionShare: + def test_init(self, onionshare_obj): + assert onionshare_obj.hidserv_dir is None + assert onionshare_obj.onion_host is None + assert onionshare_obj.stealth is None + assert onionshare_obj.cleanup_filenames == [] + assert onionshare_obj.local_only is False + + def test_set_stealth_true(self, onionshare_obj): + onionshare_obj.set_stealth(True) + assert onionshare_obj.stealth is True + assert onionshare_obj.onion.stealth is True + + def test_set_stealth_false(self, onionshare_obj): + onionshare_obj.set_stealth(False) + assert onionshare_obj.stealth is False + assert onionshare_obj.onion.stealth is False + + def test_start_onion_service(self, onionshare_obj): + onionshare_obj.set_stealth(False) + onionshare_obj.start_onion_service() + assert 17600 <= onionshare_obj.port <= 17650 + assert onionshare_obj.onion_host == 'test_service_id.onion' + + def test_start_onion_service_stealth(self, onionshare_obj): + onionshare_obj.set_stealth(True) + onionshare_obj.start_onion_service() + assert onionshare_obj.auth_string == 'TestHidServAuth' + + def test_start_onion_service_local_only(self, onionshare_obj): + onionshare_obj.local_only = True + onionshare_obj.start_onion_service() + assert onionshare_obj.onion_host == '127.0.0.1:{}'.format( + onionshare_obj.port) + + def test_cleanup(self, onionshare_obj, temp_dir_1024, temp_file_1024): + onionshare_obj.cleanup_filenames = [temp_dir_1024, temp_file_1024] + onionshare_obj.cleanup() + + assert os.path.exists(temp_dir_1024) is False + assert os.path.exists(temp_dir_1024) is False + assert onionshare_obj.cleanup_filenames == [] diff --git a/tests/test_onionshare_common.py b/tests/test_onionshare_common.py new file mode 100644 index 00000000..d70f2c0e --- /dev/null +++ b/tests/test_onionshare_common.py @@ -0,0 +1,281 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import contextlib +import inspect +import io +import os +import random +import re +import socket +import sys +import zipfile + +import pytest + +LOG_MSG_REGEX = re.compile(r""" + ^\[Jun\ 06\ 2013\ 11:05:00\] + \ TestModule\.\.dummy_func + \ at\ 0x[a-f0-9]+>(:\ TEST_MSG)?$""", re.VERBOSE) +SLUG_REGEX = re.compile(r'^([a-z]+)(-[a-z]+)?-([a-z]+)(-[a-z]+)?$') + + +# TODO: Improve the Common tests to test it all as a single class + + +class TestBuildSlug: + @pytest.mark.parametrize('test_input,expected', ( + # VALID, two lowercase words, separated by a hyphen + ('syrup-enzyme', True), + ('caution-friday', True), + + # VALID, two lowercase words, with one hyphenated compound word + ('drop-down-thimble', True), + ('unmixed-yo-yo', True), + + # VALID, two lowercase hyphenated compound words, separated by hyphen + ('yo-yo-drop-down', True), + ('felt-tip-t-shirt', True), + ('hello-world', True), + + # INVALID + ('Upper-Case', False), + ('digits-123', False), + ('too-many-hyphens-', False), + ('symbols-!@#$%', False) + )) + def test_build_slug_regex(self, test_input, expected): + """ Test that `SLUG_REGEX` accounts for the following patterns + + There are a few hyphenated words in `wordlist.txt`: + * drop-down + * felt-tip + * t-shirt + * yo-yo + + These words cause a few extra potential slug patterns: + * word-word + * hyphenated-word-word + * word-hyphenated-word + * hyphenated-word-hyphenated-word + """ + + assert bool(SLUG_REGEX.match(test_input)) == expected + + def test_build_slug_unique(self, common_obj, sys_onionshare_dev_mode): + assert common_obj.build_slug() != common_obj.build_slug() + + +class TestDirSize: + def test_temp_dir_size(self, common_obj, temp_dir_1024_delete): + """ dir_size() should return the total size (in bytes) of all files + in a particular directory. + """ + + assert common_obj.dir_size(temp_dir_1024_delete) == 1024 + + +class TestEstimatedTimeRemaining: + @pytest.mark.parametrize('test_input,expected', ( + ((2, 676, 12), '8h14m16s'), + ((14, 1049, 30), '1h26m15s'), + ((21, 450, 1), '33m42s'), + ((31, 1115, 80), '11m39s'), + ((336, 989, 32), '2m12s'), + ((603, 949, 38), '36s'), + ((971, 1009, 83), '1s') + )) + def test_estimated_time_remaining( + self, common_obj, test_input, expected, time_time_100): + assert common_obj.estimated_time_remaining(*test_input) == expected + + @pytest.mark.parametrize('test_input', ( + (10, 20, 100), # if `time_elapsed == 0` + (0, 37, 99) # if `download_rate == 0` + )) + def test_raises_zero_division_error(self, common_obj, test_input, time_time_100): + with pytest.raises(ZeroDivisionError): + common_obj.estimated_time_remaining(*test_input) + + +class TestFormatSeconds: + @pytest.mark.parametrize('test_input,expected', ( + (0, '0s'), + (26, '26s'), + (60, '1m'), + (947.35, '15m47s'), + (1847, '30m47s'), + (2193.94, '36m34s'), + (3600, '1h'), + (13426.83, '3h43m47s'), + (16293, '4h31m33s'), + (18392.14, '5h6m32s'), + (86400, '1d'), + (129674, '1d12h1m14s'), + (56404.12, '15h40m4s') + )) + def test_format_seconds(self, common_obj, test_input, expected): + assert common_obj.format_seconds(test_input) == expected + + # TODO: test negative numbers? + @pytest.mark.parametrize('test_input', ( + 'string', lambda: None, [], {}, set() + )) + def test_invalid_input_types(self, common_obj, test_input): + with pytest.raises(TypeError): + common_obj.format_seconds(test_input) + + +class TestGetAvailablePort: + @pytest.mark.parametrize('port_min,port_max', ( + (random.randint(1024, 1500), + random.randint(1800, 2048)) for _ in range(50) + )) + def test_returns_an_open_port(self, common_obj, port_min, port_max): + """ get_available_port() should return an open port within the range """ + + port = common_obj.get_available_port(port_min, port_max) + assert port_min <= port <= port_max + with socket.socket() as tmpsock: + tmpsock.bind(('127.0.0.1', port)) + + +class TestGetPlatform: + def test_darwin(self, platform_darwin, common_obj): + assert common_obj.platform == 'Darwin' + + def test_linux(self, platform_linux, common_obj): + assert common_obj.platform == 'Linux' + + def test_windows(self, platform_windows, common_obj): + assert common_obj.platform == 'Windows' + + +# TODO: double-check these tests +class TestGetResourcePath: + def test_onionshare_dev_mode(self, common_obj, sys_onionshare_dev_mode): + prefix = os.path.join( + os.path.dirname( + os.path.dirname( + os.path.abspath( + inspect.getfile( + inspect.currentframe())))), 'share') + assert ( + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == + os.path.join(prefix, 'test_filename')) + + def test_linux(self, common_obj, platform_linux, sys_argv_sys_prefix): + prefix = os.path.join(sys.prefix, 'share/onionshare') + assert ( + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == + os.path.join(prefix, 'test_filename')) + + def test_frozen_darwin(self, common_obj, platform_darwin, sys_frozen, sys_meipass): + prefix = os.path.join(sys._MEIPASS, 'share') + assert ( + common_obj.get_resource_path(os.path.join(prefix, 'test_filename')) == + os.path.join(prefix, 'test_filename')) + + +class TestGetTorPaths: + # @pytest.mark.skipif(sys.platform != 'Darwin', reason='requires MacOS') ? + def test_get_tor_paths_darwin(self, platform_darwin, common_obj, sys_frozen, sys_meipass): + base_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + common_obj.get_resource_path('')))) + tor_path = os.path.join( + base_path, 'Resources', 'Tor', 'tor') + tor_geo_ip_file_path = os.path.join( + base_path, 'Resources', 'Tor', 'geoip') + tor_geo_ipv6_file_path = os.path.join( + base_path, 'Resources', 'Tor', 'geoip6') + obfs4proxy_file_path = os.path.join( + base_path, 'Resources', 'Tor', 'obfs4proxy') + assert (common_obj.get_tor_paths() == + (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) + + # @pytest.mark.skipif(sys.platform != 'Linux', reason='requires Linux') ? + def test_get_tor_paths_linux(self, platform_linux, common_obj): + assert (common_obj.get_tor_paths() == + ('/usr/bin/tor', '/usr/share/tor/geoip', '/usr/share/tor/geoip6', '/usr/bin/obfs4proxy')) + + # @pytest.mark.skipif(sys.platform != 'Windows', reason='requires Windows') ? + def test_get_tor_paths_windows(self, platform_windows, common_obj, sys_frozen): + base_path = os.path.join( + os.path.dirname( + os.path.dirname( + common_obj.get_resource_path(''))), 'tor') + tor_path = os.path.join( + os.path.join(base_path, 'Tor'), 'tor.exe') + obfs4proxy_file_path = os.path.join( + os.path.join(base_path, 'Tor'), 'obfs4proxy.exe') + tor_geo_ip_file_path = os.path.join( + os.path.join( + os.path.join(base_path, 'Data'), 'Tor'), 'geoip') + tor_geo_ipv6_file_path = os.path.join( + os.path.join( + os.path.join(base_path, 'Data'), 'Tor'), 'geoip6') + assert (common_obj.get_tor_paths() == + (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path)) + + +class TestHumanReadableFilesize: + @pytest.mark.parametrize('test_input,expected', ( + (1024 ** 0, '1.0 B'), + (1024 ** 1, '1.0 KiB'), + (1024 ** 2, '1.0 MiB'), + (1024 ** 3, '1.0 GiB'), + (1024 ** 4, '1.0 TiB'), + (1024 ** 5, '1.0 PiB'), + (1024 ** 6, '1.0 EiB'), + (1024 ** 7, '1.0 ZiB'), + (1024 ** 8, '1.0 YiB') + )) + def test_human_readable_filesize(self, common_obj, test_input, expected): + assert common_obj.human_readable_filesize(test_input) == expected + + +class TestLog: + @pytest.mark.parametrize('test_input', ( + ('[Jun 06 2013 11:05:00]' + ' TestModule..dummy_func' + ' at 0xdeadbeef>'), + ('[Jun 06 2013 11:05:00]' + ' TestModule..dummy_func' + ' at 0xdeadbeef>: TEST_MSG') + )) + def test_log_msg_regex(self, test_input): + assert bool(LOG_MSG_REGEX.match(test_input)) + + def test_output(self, common_obj, time_strftime): + def dummy_func(): + pass + + common_obj.debug = True + + # From: https://stackoverflow.com/questions/1218933 + with io.StringIO() as buf, contextlib.redirect_stdout(buf): + common_obj.log('TestModule', dummy_func) + common_obj.log('TestModule', dummy_func, 'TEST_MSG') + output = buf.getvalue() + + line_one, line_two, _ = output.split('\n') + assert LOG_MSG_REGEX.match(line_one) + assert LOG_MSG_REGEX.match(line_two) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py new file mode 100644 index 00000000..1f1ef528 --- /dev/null +++ b/tests/test_onionshare_settings.py @@ -0,0 +1,178 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import json +import os +import tempfile + +import pytest + +from onionshare import common, settings, strings + + +@pytest.fixture +def os_path_expanduser(monkeypatch): + monkeypatch.setattr('os.path.expanduser', lambda path: path) + + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) + + +class TestSettings: + def test_init(self, settings_obj): + assert settings_obj._settings == settings_obj.default_settings == { + 'version': 'DUMMY_VERSION_1.2.3', + 'connection_type': 'bundled', + 'control_port_address': '127.0.0.1', + 'control_port_port': 9051, + 'socks_address': '127.0.0.1', + 'socks_port': 9050, + 'socket_file_path': '/var/run/tor/control', + 'auth_type': 'no_auth', + 'auth_password': '', + 'close_after_first_download': True, + 'shutdown_timeout': False, + 'use_stealth': False, + 'use_autoupdate': True, + 'autoupdate_timestamp': None, + 'no_bridges': True, + 'tor_bridges_use_obfs4': False, + 'tor_bridges_use_meek_lite_azure': False, + 'tor_bridges_use_custom_bridges': '', + 'use_legacy_v2_onions': False, + 'save_private_key': False, + 'private_key': '', + 'slug': '', + 'hidservauth_string': '', + 'downloads_dir': os.path.expanduser('~/OnionShare'), + 'receive_allow_receiver_shutdown': True, + 'public_mode': False + } + + def test_fill_in_defaults(self, settings_obj): + del settings_obj._settings['version'] + settings_obj.fill_in_defaults() + assert settings_obj._settings['version'] == 'DUMMY_VERSION_1.2.3' + + def test_load(self, settings_obj): + custom_settings = { + 'version': 'CUSTOM_VERSION', + 'socks_port': 9999, + 'use_stealth': True + } + tmp_file, tmp_file_path = tempfile.mkstemp() + with open(tmp_file, 'w') as f: + json.dump(custom_settings, f) + settings_obj.filename = tmp_file_path + settings_obj.load() + + assert settings_obj._settings['version'] == 'CUSTOM_VERSION' + assert settings_obj._settings['socks_port'] == 9999 + assert settings_obj._settings['use_stealth'] is True + + os.remove(tmp_file_path) + assert os.path.exists(tmp_file_path) is False + + def test_save(self, monkeypatch, settings_obj): + monkeypatch.setattr(strings, '_', lambda _: '') + + settings_filename = 'default_settings.json' + tmp_dir = tempfile.gettempdir() + settings_path = os.path.join(tmp_dir, settings_filename) + settings_obj.filename = settings_path + settings_obj.save() + with open(settings_path, 'r') as f: + settings = json.load(f) + + assert settings_obj._settings == settings + + os.remove(settings_path) + assert os.path.exists(settings_path) is False + + def test_get(self, settings_obj): + assert settings_obj.get('version') == 'DUMMY_VERSION_1.2.3' + assert settings_obj.get('connection_type') == 'bundled' + assert settings_obj.get('control_port_address') == '127.0.0.1' + assert settings_obj.get('control_port_port') == 9051 + assert settings_obj.get('socks_address') == '127.0.0.1' + assert settings_obj.get('socks_port') == 9050 + assert settings_obj.get('socket_file_path') == '/var/run/tor/control' + assert settings_obj.get('auth_type') == 'no_auth' + assert settings_obj.get('auth_password') == '' + assert settings_obj.get('close_after_first_download') is True + assert settings_obj.get('use_stealth') is False + assert settings_obj.get('use_autoupdate') is True + assert settings_obj.get('autoupdate_timestamp') is None + assert settings_obj.get('autoupdate_timestamp') is None + assert settings_obj.get('no_bridges') is True + assert settings_obj.get('tor_bridges_use_obfs4') is False + assert settings_obj.get('tor_bridges_use_meek_lite_azure') is False + assert settings_obj.get('tor_bridges_use_custom_bridges') == '' + + + def test_set_version(self, settings_obj): + settings_obj.set('version', 'CUSTOM_VERSION') + assert settings_obj._settings['version'] == 'CUSTOM_VERSION' + + def test_set_control_port_port(self, settings_obj): + settings_obj.set('control_port_port', 999) + assert settings_obj._settings['control_port_port'] == 999 + + settings_obj.set('control_port_port', 'NON_INTEGER') + assert settings_obj._settings['control_port_port'] == 9051 + + def test_set_socks_port(self, settings_obj): + settings_obj.set('socks_port', 888) + assert settings_obj._settings['socks_port'] == 888 + + settings_obj.set('socks_port', 'NON_INTEGER') + assert settings_obj._settings['socks_port'] == 9050 + + def test_filename_darwin( + self, + monkeypatch, + os_path_expanduser, + platform_darwin): + obj = settings.Settings(common.Common()) + assert (obj.filename == + '~/Library/Application Support/OnionShare/onionshare.json') + + def test_filename_linux( + self, + monkeypatch, + os_path_expanduser, + platform_linux): + obj = settings.Settings(common.Common()) + assert obj.filename == '~/.config/onionshare/onionshare.json' + + def test_filename_windows( + self, + monkeypatch, + platform_windows): + monkeypatch.setenv('APPDATA', 'C:') + obj = settings.Settings(common.Common()) + assert obj.filename == 'C:\\OnionShare\\onionshare.json' + + def test_set_custom_bridge(self, settings_obj): + settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E') + assert settings_obj._settings['tor_bridges_use_custom_bridges'] == 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E' diff --git a/tests/test_onionshare_strings.py b/tests/test_onionshare_strings.py new file mode 100644 index 00000000..d3d40c8f --- /dev/null +++ b/tests/test_onionshare_strings.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import types + +import pytest + +from onionshare import strings + + +# # Stub get_resource_path so it finds the correct path while running tests +# def get_resource_path(filename): +# resources_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'share') +# path = os.path.join(resources_dir, filename) +# return 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(): + assert callable(strings._) and isinstance(strings._, types.FunctionType) + + +class TestLoadStrings: + def test_load_strings_defaults_to_english( + self, common_obj, locale_en, sys_onionshare_dev_mode): + """ load_strings() loads English by default """ + strings.load_strings(common_obj) + assert strings._('preparing_files') == "Compressing files." + + + def test_load_strings_loads_other_languages( + self, common_obj, locale_fr, sys_onionshare_dev_mode): + """ load_strings() loads other languages in different locales """ + strings.load_strings(common_obj, "fr") + assert strings._('preparing_files') == "Préparation des fichiers à partager." + + def test_load_invalid_locale( + self, common_obj, locale_invalid, sys_onionshare_dev_mode): + """ load_strings() raises a KeyError for an invalid locale """ + with pytest.raises(KeyError): + strings.load_strings(common_obj, 'XX') diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py new file mode 100644 index 00000000..24a0e163 --- /dev/null +++ b/tests/test_onionshare_web.py @@ -0,0 +1,255 @@ +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" + +import contextlib +import inspect +import io +import os +import random +import re +import socket +import sys +import zipfile +import tempfile + +import pytest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare.settings import Settings + +DEFAULT_ZW_FILENAME_REGEX = re.compile(r'^onionshare_[a-z2-7]{6}.zip$') +RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') + + +def web_obj(common_obj, mode, num_files=0): + """ Creates a Web object, in either share mode or receive mode, ready for testing """ + common_obj.load_settings() + + web = Web(common_obj, False, mode) + web.generate_slug() + web.stay_open = True + web.running = True + + web.app.testing = True + + # Share mode + if mode == 'share': + # Add files + files = [] + for i in range(num_files): + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + files.append(tmp_file.name) + web.share_mode.set_file_info(files) + # Receive mode + else: + pass + + return web + + +class TestWeb: + def test_share_mode(self, common_obj): + web = web_obj(common_obj, 'share', 3) + assert web.mode is 'share' + with web.app.test_client() as c: + # Load 404 pages + res = c.get('/') + res.get_data() + assert res.status_code == 404 + + res = c.get('/invalidslug'.format(web.slug)) + res.get_data() + assert res.status_code == 404 + + # Load download page + res = c.get('/{}'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + + # Download + res = c.get('/{}/download'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + + def test_share_mode_close_after_first_download_on(self, common_obj, temp_file_1024): + web = web_obj(common_obj, 'share', 3) + web.stay_open = False + + assert web.running == True + + with web.app.test_client() as c: + # Download the first time + res = c.get('/{}/download'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + + assert web.running == False + + def test_share_mode_close_after_first_download_off(self, common_obj, temp_file_1024): + web = web_obj(common_obj, 'share', 3) + web.stay_open = True + + assert web.running == True + + with web.app.test_client() as c: + # Download the first time + res = c.get('/{}/download'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + assert res.mimetype == 'application/zip' + assert web.running == True + + def test_receive_mode(self, common_obj): + web = web_obj(common_obj, 'receive') + assert web.mode is 'receive' + + with web.app.test_client() as c: + # Load 404 pages + res = c.get('/') + res.get_data() + assert res.status_code == 404 + + res = c.get('/invalidslug'.format(web.slug)) + res.get_data() + assert res.status_code == 404 + + # Load upload page + res = c.get('/{}'.format(web.slug)) + res.get_data() + assert res.status_code == 200 + + def test_receive_mode_allow_receiver_shutdown_on(self, common_obj): + web = web_obj(common_obj, 'receive') + + common_obj.settings.set('receive_allow_receiver_shutdown', True) + + assert web.running == True + + with web.app.test_client() as c: + # Load close page + res = c.post('/{}/close'.format(web.slug)) + res.get_data() + # Should return ok, and server should stop + assert res.status_code == 200 + assert web.running == False + + def test_receive_mode_allow_receiver_shutdown_off(self, common_obj): + web = web_obj(common_obj, 'receive') + + common_obj.settings.set('receive_allow_receiver_shutdown', False) + + assert web.running == True + + with web.app.test_client() as c: + # Load close page + res = c.post('/{}/close'.format(web.slug)) + res.get_data() + # Should redirect to index, and server should still be running + assert res.status_code == 302 + assert web.running == True + + def test_public_mode_on(self, common_obj): + web = web_obj(common_obj, 'receive') + common_obj.settings.set('public_mode', True) + + with web.app.test_client() as c: + # Upload page should be accessible from / + res = c.get('/') + data1 = res.get_data() + assert res.status_code == 200 + + # /[slug] should be a 404 + res = c.get('/{}'.format(web.slug)) + data2 = res.get_data() + assert res.status_code == 404 + + def test_public_mode_off(self, common_obj): + web = web_obj(common_obj, 'receive') + common_obj.settings.set('public_mode', False) + + with web.app.test_client() as c: + # / should be a 404 + res = c.get('/') + data1 = res.get_data() + assert res.status_code == 404 + + # Upload page should be accessible from /[slug] + res = c.get('/{}'.format(web.slug)) + data2 = res.get_data() + assert res.status_code == 200 + + +class TestZipWriterDefault: + @pytest.mark.parametrize('test_input', ( + 'onionshare_{}.zip'.format(''.join( + random.choice('abcdefghijklmnopqrstuvwxyz234567') for _ in range(6) + )) for _ in range(50) + )) + def test_default_zw_filename_regex(self, test_input): + assert bool(DEFAULT_ZW_FILENAME_REGEX.match(test_input)) + + def test_zw_filename(self, default_zw): + zw_filename = os.path.basename(default_zw.zip_filename) + assert bool(DEFAULT_ZW_FILENAME_REGEX.match(zw_filename)) + + def test_zipfile_filename_matches_zipwriter_filename(self, default_zw): + assert default_zw.z.filename == default_zw.zip_filename + + def test_zipfile_allow_zip64(self, default_zw): + assert default_zw.z._allowZip64 is True + + def test_zipfile_mode(self, default_zw): + assert default_zw.z.mode == 'w' + + def test_callback(self, default_zw): + assert default_zw.processed_size_callback(None) is None + + def test_add_file(self, default_zw, temp_file_1024_delete): + default_zw.add_file(temp_file_1024_delete) + zipfile_info = default_zw.z.getinfo( + os.path.basename(temp_file_1024_delete)) + + assert zipfile_info.compress_type == zipfile.ZIP_DEFLATED + assert zipfile_info.file_size == 1024 + + def test_add_directory(self, temp_dir_1024_delete, default_zw): + previous_size = default_zw._size # size before adding directory + default_zw.add_dir(temp_dir_1024_delete) + assert default_zw._size == previous_size + 1024 + + +class TestZipWriterCustom: + @pytest.mark.parametrize('test_input', ( + Common.random_string( + random.randint(2, 50), + random.choice((None, random.randint(2, 50))) + ) for _ in range(50) + )) + def test_random_string_regex(self, test_input): + assert bool(RANDOM_STR_REGEX.match(test_input)) + + def test_custom_filename(self, custom_zw): + assert bool(RANDOM_STR_REGEX.match(custom_zw.zip_filename)) + + def test_custom_callback(self, custom_zw): + assert custom_zw.processed_size_callback(None) == 'custom_callback' diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py new file mode 100644 index 00000000..de1ad9ab --- /dev/null +++ b/tests_gui_local/commontests.py @@ -0,0 +1,307 @@ +import os +import requests +import socket +import socks +import zipfile + +from PyQt5 import QtCore, QtTest +from onionshare import strings + +class CommonTests(object): + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_info_widget_is_not_visible(self, mode): + '''Test that the info widget along top of screen is not shown''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.info_widget.isVisible()) + + def test_info_widget_is_visible(self, mode): + '''Test that the info widget along top of screen is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible and that the relevant widget is present''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + if mode == 'share': + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if mode == 'receive': + if not public_mode: + self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + if mode == 'share': + if not public_mode: + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + if mode == 'share': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + if mode == 'receive': + path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) + if mode == 'share': + path = '/{}'.format(self.gui.share_mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.uploads_completed, count) + if mode == 'share': + self.assertEquals(self.gui.share_mode.downloads_completed, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + if stay_open: + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + if mode == 'share': + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + # Auto-stop timer tests + def test_set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + if mode == 'receive': + self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) + if mode == 'share': + self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + diff --git a/tests_gui_local/conftest.py b/tests_gui_local/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/tests_gui_local/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..bac622fb --- /dev/null +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + CommonTests.test_info_widget_is_not_visible(self, 'receive') + + @pytest.mark.run(order=6) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'receive') + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'receive') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'receive', False) + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'receive') + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'receive') + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') + + @pytest.mark.run(order=18) + def test_web_page(self): + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) + + @pytest.mark.run(order=19) + def test_upload_file(self): + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') + + @pytest.mark.run(order=20) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') + + @pytest.mark.run(order=21) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + CommonTests.test_counter_incremented(self, 'receive', 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'receive', False) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py new file mode 100644 index 00000000..8ed385f5 --- /dev/null +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + CommonTests.test_info_widget_is_not_visible(self, 'receive') + + @pytest.mark.run(order=6) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'receive') + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'receive') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'receive', True) + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'receive') + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'receive') + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') + + @pytest.mark.run(order=18) + def test_web_page(self): + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) + + @pytest.mark.run(order=19) + def test_upload_file(self): + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') + + @pytest.mark.run(order=20) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') + + @pytest.mark.run(order=21) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + CommonTests.test_counter_incremented(self, 'receive', 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'receive', False) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..0ef03e97 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=19) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=20) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=21) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', False) + + @pytest.mark.run(order=22) + def test_download_share(self): + CommonTests.test_download_share(self, False) + + @pytest.mark.run(order=23) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + + @pytest.mark.run(order=27) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py new file mode 100644 index 00000000..417eb8b9 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) + + @pytest.mark.run(order=18) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=19) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=20) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=21) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) + + @pytest.mark.run(order=22) + def test_download_share(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=23) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + + @pytest.mark.run(order=27) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py new file mode 100644 index 00000000..c3177a38 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) + + @pytest.mark.run(order=18) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=19) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=20) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=21) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) + + @pytest.mark.run(order=22) + def test_download_share(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=23) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=24) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'share', 1) + + @pytest.mark.run(order=25) + def test_download_share_again(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=26) + def test_counter_incremented_again(self): + CommonTests.test_counter_incremented(self, 'share', 2) + + @pytest.mark.run(order=27) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=28) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=29) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + + @pytest.mark.run(order=30) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py new file mode 100644 index 00000000..05c9719a --- /dev/null +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + slug = '' + + @classmethod + 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 = { + "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, + "receive_allow_receiver_shutdown": True, + "save_private_key": True, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=6) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=8) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=9) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=10) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=11) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=12) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=13) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=14) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=15) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=16) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + + @pytest.mark.run(order=17) + def test_server_started_again(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=18) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + + @pytest.mark.run(order=19) + def test_server_is_stopped_again(self): + CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_web_service_is_stopped(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py new file mode 100644 index 00000000..f36331b8 --- /dev/null +++ b/tests_gui_local/onionshare_timer_test.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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 = { + "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, + "receive_allow_receiver_shutdown": 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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_set_timeout(self): + CommonTests.test_set_timeout(self, 'share', 5) + + @pytest.mark.run(order=9) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=10) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=13) + def test_timeout_widget_hidden(self): + CommonTests.test_timeout_widget_hidden(self, 'share') + + @pytest.mark.run(order=14) + def test_timeout(self): + CommonTests.test_server_timed_out(self, 'share', 10000) + + @pytest.mark.run(order=15) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/run_unit_tests.sh b/tests_gui_local/run_unit_tests.sh new file mode 100755 index 00000000..d15f8a6e --- /dev/null +++ b/tests_gui_local/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + py.test-3 $test -vvv || exit 1 +done diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/unit_tests/commontests.py b/unit_tests/commontests.py deleted file mode 100644 index de1ad9ab..00000000 --- a/unit_tests/commontests.py +++ /dev/null @@ -1,307 +0,0 @@ -import os -import requests -import socket -import socks -import zipfile - -from PyQt5 import QtCore, QtTest -from onionshare import strings - -class CommonTests(object): - def test_gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - def test_info_widget_is_not_visible(self, mode): - '''Test that the info widget along top of screen is not shown''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.info_widget.isVisible()) - - def test_info_widget_is_visible(self, mode): - '''Test that the info widget along top of screen is shown''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) - - def test_click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if mode == 'share': - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - def test_history_is_visible(self, mode): - '''Test that the History section is visible and that the relevant widget is present''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - - def test_server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.receive_mode.server_status.status, 1) - if mode == 'share': - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.status, 1) - - def test_server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) - if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) - - def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - def test_a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 2) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 2) - - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if mode == 'receive': - if not public_mode: - self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - if mode == 'share': - if not public_mode: - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') - - - def test_url_description_shown(self, mode): - '''Test that the URL label is showing''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) - - def test_have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) - - def test_server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) - if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) - - def test_web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - if mode == 'receive': - path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) - if mode == 'share': - path = '/{}'.format(self.gui.share_mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def test_history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) - - def test_counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.uploads_completed, count) - if mode == 'share': - self.assertEquals(self.gui.share_mode.downloads_completed, count) - - def test_server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - if stay_open: - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.status, 0) - - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) - if mode == 'share': - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) - - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - if mode == 'receive': - self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) - if mode == 'share': - self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py deleted file mode 100644 index 8ac7efb8..00000000 --- a/unit_tests/conftest.py +++ /dev/null @@ -1,160 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - return common.Common() - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/unit_tests/onionshare_receive_mode_upload_test.py b/unit_tests/onionshare_receive_mode_upload_test.py deleted file mode 100644 index bac622fb..00000000 --- a/unit_tests/onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - - @pytest.mark.run(order=6) - def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') - - @pytest.mark.run(order=8) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - - @pytest.mark.run(order=9) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') - - @pytest.mark.run(order=10) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', False) - - @pytest.mark.run(order=15) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') - - @pytest.mark.run(order=16) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') - - @pytest.mark.run(order=17) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') - - @pytest.mark.run(order=18) - def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) - - @pytest.mark.run(order=19) - def test_upload_file(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=20) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') - - @pytest.mark.run(order=21) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) - - @pytest.mark.run(order=22) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=23) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) - - @pytest.mark.run(order=24) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) - - @pytest.mark.run(order=25) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=26) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py deleted file mode 100644 index 8ed385f5..00000000 --- a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - - @pytest.mark.run(order=6) - def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') - - @pytest.mark.run(order=8) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - - @pytest.mark.run(order=9) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') - - @pytest.mark.run(order=10) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', True) - - @pytest.mark.run(order=15) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') - - @pytest.mark.run(order=16) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') - - @pytest.mark.run(order=17) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') - - @pytest.mark.run(order=18) - def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) - - @pytest.mark.run(order=19) - def test_upload_file(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=20) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') - - @pytest.mark.run(order=21) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) - - @pytest.mark.run(order=22) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=23) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) - - @pytest.mark.run(order=24) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) - - @pytest.mark.run(order=25) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=26) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test.py b/unit_tests/onionshare_share_mode_download_test.py deleted file mode 100644 index 0ef03e97..00000000 --- a/unit_tests/onionshare_share_mode_download_test.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=15) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=18) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=19) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=20) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=21) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', False) - - @pytest.mark.run(order=22) - def test_download_share(self): - CommonTests.test_download_share(self, False) - - @pytest.mark.run(order=23) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=24) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=25) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=26) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - - @pytest.mark.run(order=27) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_public_mode.py b/unit_tests/onionshare_share_mode_download_test_public_mode.py deleted file mode 100644 index 417eb8b9..00000000 --- a/unit_tests/onionshare_share_mode_download_test_public_mode.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=15) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) - - @pytest.mark.run(order=18) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=19) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=20) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=21) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) - - @pytest.mark.run(order=22) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=23) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=24) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=25) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=26) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - - @pytest.mark.run(order=27) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_stay_open.py b/unit_tests/onionshare_share_mode_download_test_stay_open.py deleted file mode 100644 index c3177a38..00000000 --- a/unit_tests/onionshare_share_mode_download_test_stay_open.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=15) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) - - @pytest.mark.run(order=18) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=19) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=20) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=21) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) - - @pytest.mark.run(order=22) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=23) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=24) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'share', 1) - - @pytest.mark.run(order=25) - def test_download_share_again(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=26) - def test_counter_incremented_again(self): - CommonTests.test_counter_incremented(self, 'share', 2) - - @pytest.mark.run(order=27) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) - - @pytest.mark.run(order=28) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=29) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - - @pytest.mark.run(order=30) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_slug_persistent_test.py b/unit_tests/onionshare_slug_persistent_test.py deleted file mode 100644 index 05c9719a..00000000 --- a/unit_tests/onionshare_slug_persistent_test.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - slug = '' - - @classmethod - 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 = { - "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, - "receive_allow_receiver_shutdown": True, - "save_private_key": True, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=6) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=8) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=9) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=10) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=11) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=12) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=13) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=14) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) - - @pytest.mark.run(order=15) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=16) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - - @pytest.mark.run(order=17) - def test_server_started_again(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_server_status_indicator_says_starting(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - - @pytest.mark.run(order=19) - def test_server_is_stopped_again(self): - CommonTests.test_server_is_stopped(self, 'share', True) - CommonTests.test_web_service_is_stopped(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/onionshare_timer_test.py b/unit_tests/onionshare_timer_test.py deleted file mode 100644 index f36331b8..00000000 --- a/unit_tests/onionshare_timer_test.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -app = QtWidgets.QApplication(sys.argv) - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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 = { - "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, - "receive_allow_receiver_shutdown": 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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_set_timeout(self): - CommonTests.test_set_timeout(self, 'share', 5) - - @pytest.mark.run(order=9) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=10) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=13) - def test_timeout_widget_hidden(self): - CommonTests.test_timeout_widget_hidden(self, 'share') - - @pytest.mark.run(order=14) - def test_timeout(self): - CommonTests.test_server_timed_out(self, 'share', 10000) - - @pytest.mark.run(order=15) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - -if __name__ == "__main__": - unittest.main() diff --git a/unit_tests/run_unit_tests.sh b/unit_tests/run_unit_tests.sh deleted file mode 100755 index d15f8a6e..00000000 --- a/unit_tests/run_unit_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^onionshare_`; do - py.test-3 $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From 8212da2d3df36c17162225f83be12a9db09cbccb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 27 Sep 2018 17:33:15 +1000 Subject: Add Tor GUI unit tests --- BUILD.md | 9 + tests_gui_tor/__init__.py | 0 tests_gui_tor/commontests.py | 359 +++++++++++++++++++++ tests_gui_tor/conftest.py | 160 +++++++++ .../onionshare_receive_mode_upload_test.py | 188 +++++++++++ ...onshare_receive_mode_upload_test_public_mode.py | 188 +++++++++++ .../onionshare_share_mode_cancel_share_test.py | 159 +++++++++ .../onionshare_share_mode_download_test.py | 195 +++++++++++ ...onshare_share_mode_download_test_public_mode.py | 195 +++++++++++ ...nionshare_share_mode_download_test_stay_open.py | 207 ++++++++++++ .../onionshare_share_mode_persistent_test.py | 179 ++++++++++ .../onionshare_share_mode_stealth_test.py | 174 ++++++++++ ...nshare_share_mode_tor_connection_killed_test.py | 179 ++++++++++ tests_gui_tor/onionshare_timer_test.py | 142 ++++++++ .../onionshare_tor_connection_killed_test.py | 179 ++++++++++ tests_gui_tor/run_unit_tests.sh | 5 + 16 files changed, 2518 insertions(+) create mode 100644 tests_gui_tor/__init__.py create mode 100644 tests_gui_tor/commontests.py create mode 100644 tests_gui_tor/conftest.py create mode 100644 tests_gui_tor/onionshare_receive_mode_upload_test.py create mode 100644 tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 tests_gui_tor/onionshare_share_mode_cancel_share_test.py create mode 100644 tests_gui_tor/onionshare_share_mode_download_test.py create mode 100644 tests_gui_tor/onionshare_share_mode_download_test_public_mode.py create mode 100644 tests_gui_tor/onionshare_share_mode_download_test_stay_open.py create mode 100644 tests_gui_tor/onionshare_share_mode_persistent_test.py create mode 100644 tests_gui_tor/onionshare_share_mode_stealth_test.py create mode 100644 tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py create mode 100644 tests_gui_tor/onionshare_timer_test.py create mode 100644 tests_gui_tor/onionshare_tor_connection_killed_test.py create mode 100755 tests_gui_tor/run_unit_tests.sh diff --git a/BUILD.md b/BUILD.md index 308a186c..00d24cd2 100644 --- a/BUILD.md +++ b/BUILD.md @@ -155,3 +155,12 @@ If you would like to run the GUI unit tests in 'local only mode': cd tests_gui_local/ ./run_unit_tests.sh ``` + +If you would like to run the GUI unit tests in 'tor' (bundled) mode: + +```sh +cd tests_gui_tor/ +./run_unit_tests.sh +``` + +Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive. diff --git a/tests_gui_tor/__init__.py b/tests_gui_tor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py new file mode 100644 index 00000000..a0d9bf5f --- /dev/null +++ b/tests_gui_tor/commontests.py @@ -0,0 +1,359 @@ +import os +import requests +import socket +import socks +import zipfile + +from PyQt5 import QtCore, QtTest +from onionshare import strings + +class CommonTests(object): + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_info_widget_is_not_visible(self, mode): + '''Test that the info widget along top of screen is not shown''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.info_widget.isVisible()) + + def test_info_widget_is_visible(self, mode): + '''Test that the info widget along top of screen is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible and that the relevant widget is present''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + if mode == 'share': + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + if mode == 'share': + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(45000) + # Should now be in SERVER_STARTED state + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if mode == 'receive': + if not public_mode: + self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + if mode == 'share': + if not public_mode: + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + def test_have_an_onion_service(self): + '''Test that we have a valid Onion URL''' + self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + if mode == 'share': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) + + s = socks.socksocket() + s.settimeout(60) + s.connect((self.gui.app.onion_host, 80)) + + if not public_mode: + if mode == 'receive': + path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) + if mode == 'share': + path = '/{}'.format(self.gui.share_mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.uploads_completed, count) + if mode == 'share': + self.assertEquals(self.gui.share_mode.downloads_completed, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if mode == 'receive': + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + if stay_open: + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if mode == 'receive': + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + if mode == 'share': + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + def test_cancel_the_share(self, mode): + '''Test that we can cancel this share before it's started up ''' + if mode == 'share': + QtTest.QTest.mousePress(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(1000) + QtTest.QTest.mouseRelease(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + if mode == 'receive': + QtTest.QTest.mousePress(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(1000) + QtTest.QTest.mouseRelease(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.receive_mode.server_status.status, 0) + + + # Auto-stop timer tests + def test_set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + if mode == 'receive': + self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) + if mode == 'share': + self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 0) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) + else: + path = 'http://{}/upload'.format(self.gui.app.onion_host) + response = session.post(path, files=files) + QtTest.QTest.qWait(4000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) + + s = socks.socksocket() + s.settimeout(60) + s.connect((self.gui.app.onion_host, 80)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(4000) + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + # Stealth tests + def test_copy_have_hidserv_auth_button(self, mode): + '''Test that the Copy HidservAuth button is shown''' + if mode == 'share': + self.assertTrue(self.gui.share_mode.server_status.copy_hidservauth_button.isVisible()) + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.server_status.copy_hidservauth_button.isVisible()) + + def test_hidserv_auth_string(self): + '''Test the validity of the HidservAuth string''' + self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) + + + # Miscellaneous tests + def test_tor_killed_statusbar_message_shown(self, mode): + '''Test that the status bar message shows Tor was disconnected''' + self.gui.app.onion.cleanup(stop_tor=True) + QtTest.QTest.qWait(2500) + if mode == 'share': + self.assertTrue(self.gui.share_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True)) + if mode == 'receive': + self.assertTrue(self.gui.receive_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost', True)) diff --git a/tests_gui_tor/conftest.py b/tests_gui_tor/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/tests_gui_tor/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test.py b/tests_gui_tor/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..5c2945f1 --- /dev/null +++ b/tests_gui_tor/onionshare_receive_mode_upload_test.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + CommonTests.test_info_widget_is_not_visible(self, 'receive') + + @pytest.mark.run(order=6) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'receive') + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'receive') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'receive', False) + + @pytest.mark.run(order=15) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=16) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'receive') + + @pytest.mark.run(order=17) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'receive') + + @pytest.mark.run(order=18) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') + + @pytest.mark.run(order=19) + def test_web_page(self): + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) + + @pytest.mark.run(order=20) + def test_upload_file(self): + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') + + @pytest.mark.run(order=21) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') + + @pytest.mark.run(order=22) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) + + @pytest.mark.run(order=23) + def test_upload_same_file_is_renamed(self): + CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=24) + def test_upload_count_incremented_again(self): + CommonTests.test_counter_incremented(self, 'receive', 2) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'receive', False) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py new file mode 100644 index 00000000..86cde0d9 --- /dev/null +++ b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + CommonTests.test_info_widget_is_not_visible(self, 'receive') + + @pytest.mark.run(order=6) + def test_click_mode(self): + CommonTests.test_click_mode(self, 'receive') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'receive') + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'receive') + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'receive') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'receive', True) + + @pytest.mark.run(order=15) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=16) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'receive') + + @pytest.mark.run(order=17) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'receive') + + @pytest.mark.run(order=18) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'receive') + + @pytest.mark.run(order=19) + def test_web_page(self): + CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) + + @pytest.mark.run(order=20) + def test_upload_file(self): + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') + + @pytest.mark.run(order=21) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'receive') + + @pytest.mark.run(order=22) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'receive', 1) + + @pytest.mark.run(order=23) + def test_upload_same_file_is_renamed(self): + CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=24) + def test_upload_count_incremented_again(self): + CommonTests.test_counter_incremented(self, 'receive', 2) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'receive', False) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py new file mode 100644 index 00000000..a2d1a06a --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_cancel_the_share(self): + CommonTests.test_cancel_the_share(self, 'share') + + @pytest.mark.run(order=18) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=19) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=20) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test.py b/tests_gui_tor/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..d1eb5b54 --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_download_test.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', False) + + @pytest.mark.run(order=23) + def test_download_share(self): + CommonTests.test_download_share(self, False) + + @pytest.mark.run(order=24) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + + @pytest.mark.run(order=28) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py new file mode 100644 index 00000000..4e5f1114 --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) + + @pytest.mark.run(order=23) + def test_download_share(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=24) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + + @pytest.mark.run(order=28) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py new file mode 100644 index 00000000..78cd1578 --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', True) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_web_page(self): + CommonTests.test_web_page(self, 'share', 'Total size', True) + + @pytest.mark.run(order=23) + def test_download_share(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=24) + def test_history_widgets_present(self): + CommonTests.test_history_widgets_present(self, 'share') + + @pytest.mark.run(order=25) + def test_counter_incremented(self): + CommonTests.test_counter_incremented(self, 'share', 1) + + @pytest.mark.run(order=26) + def test_download_share_again(self): + CommonTests.test_download_share(self, True) + + @pytest.mark.run(order=27) + def test_counter_incremented_again(self): + CommonTests.test_counter_incremented(self, 'share', 2) + + @pytest.mark.run(order=28) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=29) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=30) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + + @pytest.mark.run(order=31) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_persistent_test.py b/tests_gui_tor/onionshare_share_mode_persistent_test.py new file mode 100644 index 00000000..a2d429b2 --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_persistent_test.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + slug = '' + onion_host = '' + + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "receive_allow_receiver_shutdown": True, + "save_private_key": True, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=6) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=8) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=9) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=10) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=11) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=12) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=13) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + global onion_host + onion_host = self.gui.app.onion_host + + @pytest.mark.run(order=14) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=15) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=16) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_closed(self): + CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + + @pytest.mark.run(order=18) + def test_server_started_again(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=19) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + + @pytest.mark.run(order=20) + def test_have_same_onion(self): + '''Test that we have the same onion''' + self.assertEqual(self.gui.app.onion_host, onion_host) + + @pytest.mark.run(order=21) + def test_server_is_stopped_again(self): + CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_web_service_is_stopped(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_stealth_test.py b/tests_gui_tor/onionshare_share_mode_stealth_test.py new file mode 100644 index 00000000..948e834a --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_stealth_test.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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": True, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_copy_have_hidserv_auth_button(self): + CommonTests.test_copy_have_hidserv_auth_button(self, 'share') + + @pytest.mark.run(order=23) + def test_hidserv_auth_string(self): + CommonTests.test_hidserv_auth_string(self) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py new file mode 100644 index 00000000..3eeea9bc --- /dev/null +++ b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_tor_killed_statusbar_message_shown(self): + CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') + + @pytest.mark.run(order=23) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=24) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_timer_test.py b/tests_gui_tor/onionshare_timer_test.py new file mode 100644 index 00000000..865b3a8b --- /dev/null +++ b/tests_gui_tor/onionshare_timer_test.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "receive_allow_receiver_shutdown": 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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_set_timeout(self): + CommonTests.test_set_timeout(self, 'share', 120) + + @pytest.mark.run(order=9) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=10) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=13) + def test_timeout_widget_hidden(self): + CommonTests.test_timeout_widget_hidden(self, 'share') + + @pytest.mark.run(order=14) + def test_timeout(self): + CommonTests.test_server_timed_out(self, 'share', 125000) + + @pytest.mark.run(order=15) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/onionshare_tor_connection_killed_test.py b/tests_gui_tor/onionshare_tor_connection_killed_test.py new file mode 100644 index 00000000..3eeea9bc --- /dev/null +++ b/tests_gui_tor/onionshare_tor_connection_killed_test.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_tor_killed_statusbar_message_shown(self): + CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') + + @pytest.mark.run(order=23) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=24) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_tor/run_unit_tests.sh b/tests_gui_tor/run_unit_tests.sh new file mode 100755 index 00000000..d15f8a6e --- /dev/null +++ b/tests_gui_tor/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + py.test-3 $test -vvv || exit 1 +done -- cgit v1.2.3-54-g00ecf From f0dd76f681cce41a40e8c821c2b401e07a15f09b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 17:34:46 -0700 Subject: Remove all the extra QApplications --- tests_gui_local/onionshare_receive_mode_upload_test.py | 6 ++---- tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py | 6 ++---- tests_gui_local/onionshare_share_mode_download_test.py | 4 +--- tests_gui_local/onionshare_share_mode_download_test_public_mode.py | 4 +--- tests_gui_local/onionshare_share_mode_download_test_stay_open.py | 4 +--- tests_gui_local/onionshare_slug_persistent_test.py | 4 +--- tests_gui_local/onionshare_timer_test.py | 4 +--- tests_gui_tor/onionshare_receive_mode_upload_test.py | 6 ++---- tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py | 6 ++---- tests_gui_tor/onionshare_share_mode_cancel_share_test.py | 4 +--- tests_gui_tor/onionshare_share_mode_download_test.py | 4 +--- tests_gui_tor/onionshare_share_mode_download_test_public_mode.py | 4 +--- tests_gui_tor/onionshare_share_mode_download_test_stay_open.py | 4 +--- tests_gui_tor/onionshare_share_mode_persistent_test.py | 4 +--- tests_gui_tor/onionshare_share_mode_stealth_test.py | 4 +--- tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py | 4 +--- tests_gui_tor/onionshare_timer_test.py | 4 +--- tests_gui_tor/onionshare_tor_connection_killed_test.py | 4 +--- 18 files changed, 22 insertions(+), 58 deletions(-) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index bac622fb..2aa2ed94 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, @@ -72,7 +70,7 @@ class OnionShareGuiTest(unittest.TestCase): open(testsettings, 'w').write(json.dumps(test_settings)) cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - + @classmethod def tearDownClass(cls): '''Clean up after tests''' diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 8ed385f5..30a290e7 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, @@ -72,7 +70,7 @@ class OnionShareGuiTest(unittest.TestCase): open(testsettings, 'w').write(json.dumps(test_settings)) cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) - + @classmethod def tearDownClass(cls): '''Clean up after tests''' diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 0ef03e97..c546fb61 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index 417eb8b9..764b5885 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index c3177a38..b92ff097 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index 05c9719a..1e5614dc 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' slug = '' @@ -50,7 +48,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py index f36331b8..1a5134e2 100644 --- a/tests_gui_local/onionshare_timer_test.py +++ b/tests_gui_local/onionshare_timer_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test.py b/tests_gui_tor/onionshare_receive_mode_upload_test.py index 5c2945f1..5be400e2 100644 --- a/tests_gui_tor/onionshare_receive_mode_upload_test.py +++ b/tests_gui_tor/onionshare_receive_mode_upload_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, @@ -72,7 +70,7 @@ class OnionShareGuiTest(unittest.TestCase): open(testsettings, 'w').write(json.dumps(test_settings)) cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - + @classmethod def tearDownClass(cls): '''Clean up after tests''' diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py index 86cde0d9..9c9553a4 100644 --- a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, @@ -72,7 +70,7 @@ class OnionShareGuiTest(unittest.TestCase): open(testsettings, 'w').write(json.dumps(test_settings)) cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - + @classmethod def tearDownClass(cls): '''Clean up after tests''' diff --git a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py index a2d1a06a..466109d7 100644 --- a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py +++ b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_download_test.py b/tests_gui_tor/onionshare_share_mode_download_test.py index d1eb5b54..1c8e1b6c 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test.py +++ b/tests_gui_tor/onionshare_share_mode_download_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py index 4e5f1114..c292e729 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py index 78cd1578..7838316f 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": True, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_persistent_test.py b/tests_gui_tor/onionshare_share_mode_persistent_test.py index a2d429b2..3cffaab6 100644 --- a/tests_gui_tor/onionshare_share_mode_persistent_test.py +++ b/tests_gui_tor/onionshare_share_mode_persistent_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' slug = '' @@ -51,7 +49,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_stealth_test.py b/tests_gui_tor/onionshare_share_mode_stealth_test.py index 948e834a..aaf6fbc6 100644 --- a/tests_gui_tor/onionshare_share_mode_stealth_test.py +++ b/tests_gui_tor/onionshare_share_mode_stealth_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py index 3eeea9bc..861b7ccc 100644 --- a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_timer_test.py b/tests_gui_tor/onionshare_timer_test.py index 865b3a8b..b76106d9 100644 --- a/tests_gui_tor/onionshare_timer_test.py +++ b/tests_gui_tor/onionshare_timer_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, diff --git a/tests_gui_tor/onionshare_tor_connection_killed_test.py b/tests_gui_tor/onionshare_tor_connection_killed_test.py index 3eeea9bc..861b7ccc 100644 --- a/tests_gui_tor/onionshare_tor_connection_killed_test.py +++ b/tests_gui_tor/onionshare_tor_connection_killed_test.py @@ -14,8 +14,6 @@ from onionshare_gui import * from .commontests import CommonTests -app = QtWidgets.QApplication(sys.argv) - class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod @@ -48,7 +46,7 @@ class OnionShareGuiTest(unittest.TestCase): "control_port_port": 9051, "downloads_dir": "/tmp/OnionShare", "hidservauth_string": "", - "no_bridges": True, + "no_bridges": True, "private_key": "", "public_mode": False, "receive_allow_receiver_shutdown": True, -- cgit v1.2.3-54-g00ecf From 8261b4868d5c05ad8f52c9f0efb4f038c3062879 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 17:51:16 -0700 Subject: Add @mig5 as a code owner for all tests, and add @emmapeel2 as a code owner for locales --- .github/CODEOWNERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1cd5a1f6..42d1840f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,9 @@ * @micahflee + +# localization +/share/locale/ @emmapeel2 + +# tests +/tests/ @mig5 +/tests_gui_local/ @mig5 +/tests_gui_tor/ @mig5 -- cgit v1.2.3-54-g00ecf From 6f57f7eae640b08e5be3a76da1663f97a4d15cdd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 21:19:42 -0700 Subject: Update Travis CI to run GUI tests --- .travis.yml | 12 ++++++++---- tests_gui_local/run_unit_tests.sh | 2 +- tests_gui_tor/run_unit_tests.sh | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a41339cc..aa1ff102 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python -# sudo: required -dist: bionic +dist: trusty +sudo: required python: - "3.6" - "3.6-dev" @@ -8,14 +8,18 @@ python: - "nightly" # command to install dependencies install: + - sudo apt-get update && sudo apt-get install python3-pyqt5 - pip install -r install/requirements.txt + - pip install -r install/requirements-tests.txt - pip install pytest-cov coveralls flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -# command to run tests -script: pytest --cov=onionshare tests/ +# run CLI tests and local GUI tests +script: + - pytest --cov=onionshare tests/ + - cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh after_success: - coveralls diff --git a/tests_gui_local/run_unit_tests.sh b/tests_gui_local/run_unit_tests.sh index d15f8a6e..7d207a57 100755 --- a/tests_gui_local/run_unit_tests.sh +++ b/tests_gui_local/run_unit_tests.sh @@ -1,5 +1,5 @@ #!/bin/bash for test in `ls -1 | egrep ^onionshare_`; do - py.test-3 $test -vvv || exit 1 + pytest $test -vvv || exit 1 done diff --git a/tests_gui_tor/run_unit_tests.sh b/tests_gui_tor/run_unit_tests.sh index d15f8a6e..7d207a57 100755 --- a/tests_gui_tor/run_unit_tests.sh +++ b/tests_gui_tor/run_unit_tests.sh @@ -1,5 +1,5 @@ #!/bin/bash for test in `ls -1 | egrep ^onionshare_`; do - py.test-3 $test -vvv || exit 1 + pytest $test -vvv || exit 1 done -- cgit v1.2.3-54-g00ecf From 75153db11e81972621c920788c3ad432e7b4a152 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 21:22:10 -0700 Subject: Keep trying ports until it finds a free one --- onionshare/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/common.py b/onionshare/common.py index 0ce411e8..28b282c2 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -433,7 +433,7 @@ class Common(object): tmpsock.bind(("127.0.0.1", random.randint(min_port, max_port))) break except OSError as e: - raise OSError(e) + pass _, port = tmpsock.getsockname() return port -- cgit v1.2.3-54-g00ecf From aa7919abfd59629b985ed0ec60754ba6b709467c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 21:27:29 -0700 Subject: Remove submitting to coveralls --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index aa1ff102..e0b5b822 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - sudo apt-get update && sudo apt-get install python3-pyqt5 - pip install -r install/requirements.txt - pip install -r install/requirements-tests.txt - - pip install pytest-cov coveralls flake8 + - pip install pytest-cov flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -21,5 +21,3 @@ before_script: script: - pytest --cov=onionshare tests/ - cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh -after_success: - - coveralls -- cgit v1.2.3-54-g00ecf From 2ffcdbb1083dece7664792f7bef9dbf2245e549e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 27 Sep 2018 21:34:39 -0700 Subject: One script to run all tests --- dev_scripts/run_all_tests.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 dev_scripts/run_all_tests.sh diff --git a/dev_scripts/run_all_tests.sh b/dev_scripts/run_all_tests.sh new file mode 100755 index 00000000..90ef1dc0 --- /dev/null +++ b/dev_scripts/run_all_tests.sh @@ -0,0 +1,14 @@ +#!/bin/bash +ROOT="$( dirname $(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd ))" + +# CLI tests +cd $ROOT +pytest tests/ + +# Local GUI tests +cd $ROOT/tests_gui_local +./run_unit_tests.sh + +# Tor GUI tests +cd $ROOT/tests_gui_tor +./run_unit_tests.sh -- cgit v1.2.3-54-g00ecf From fc1902c1ee768e6573324d1ad0cfb8d086c432cd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 12:51:30 -0700 Subject: Refactor how Mode layouts work, so the downstream Mode has more control over the UI --- onionshare_gui/mode.py | 19 ++++++------------- onionshare_gui/receive_mode/__init__.py | 16 ++++++++++++---- onionshare_gui/share_mode/__init__.py | 14 +++++++++++--- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 6b156f7e..d91b2e64 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -72,24 +72,17 @@ class Mode(QtWidgets.QWidget): self.starting_server_step3.connect(self.start_server_step3) self.starting_server_error.connect(self.start_server_error) - # Primary action layout + # Primary action + # Note: It's up to the downstream Mode to add this to its layout self.primary_action_layout = QtWidgets.QVBoxLayout() self.primary_action_layout.addWidget(self.server_status) self.primary_action = QtWidgets.QWidget() self.primary_action.setLayout(self.primary_action_layout) - # Layout - self.layout = QtWidgets.QVBoxLayout() - self.layout.addWidget(self.primary_action) - # Hack to allow a minimum width on self.layout - min_width_widget = QtWidgets.QWidget() - min_width_widget.setMinimumWidth(450) - self.layout.addWidget(min_width_widget) - - self.horizontal_layout_wrapper = QtWidgets.QHBoxLayout() - self.horizontal_layout_wrapper.addLayout(self.layout) - - self.setLayout(self.horizontal_layout_wrapper) + # Hack to allow a minimum width on the main layout + # Note: It's up to the downstream Mode to add this to its layout + self.min_width_widget = QtWidgets.QWidget() + self.min_width_widget.setMinimumWidth(450) def init(self): """ diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 590dec65..f2a82e54 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -76,11 +76,19 @@ class ReceiveMode(Mode): self.receive_info.setMinimumHeight(80) self.receive_info.setWordWrap(True) + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addWidget(self.info_widget) + self.main_layout.addWidget(self.receive_info) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addStretch() + self.main_layout.addWidget(self.min_width_widget) + # Layout - self.layout.insertWidget(0, self.receive_info) - self.layout.insertWidget(0, self.info_widget) - self.layout.addStretch() - self.horizontal_layout_wrapper.addWidget(self.uploads) + self.layout = QtWidgets.QHBoxLayout() + self.layout.addLayout(self.main_layout) + self.layout.addWidget(self.uploads) + self.setLayout(self.layout) def get_stop_server_shutdown_timeout_text(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 90fce49a..8a937f1d 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -106,10 +106,18 @@ class ShareMode(Mode): # Status bar, zip progress bar self._zip_progress_bar = None + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addWidget(self.info_widget) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + # Layout - self.layout.insertLayout(0, self.file_selection) - self.layout.insertWidget(0, self.info_widget) - self.horizontal_layout_wrapper.addWidget(self.downloads) + self.layout = QtWidgets.QHBoxLayout() + self.layout.addLayout(self.main_layout) + self.layout.addWidget(self.downloads) + self.setLayout(self.layout) # Always start with focus on file selection self.file_selection.setFocus() -- cgit v1.2.3-54-g00ecf From ddcbed451c2697e8b39ad7baf47c1518dd32b5d5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 13:18:18 -0700 Subject: Hide the uploads and downloads by default, and make the mode switcher hide before showing, to prevent weird window resizing --- onionshare_gui/onionshare_gui.py | 3 +-- onionshare_gui/receive_mode/__init__.py | 1 + onionshare_gui/share_mode/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 83f3a7e0..79565edd 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -55,7 +55,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setWindowTitle('OnionShare') self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) - self.setMinimumWidth(850) # Load settings self.config = config @@ -194,8 +193,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) - self.share_mode.show() self.receive_mode.hide() + self.share_mode.show() else: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index f2a82e54..80bd9cad 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -48,6 +48,7 @@ class ReceiveMode(Mode): # Uploads self.uploads = Uploads(self.common) + self.uploads.hide() self.uploads_in_progress = 0 self.uploads_completed = 0 self.new_upload = False # For scrolling to the bottom of the uploads list diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 8a937f1d..c097d75a 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -72,6 +72,7 @@ class ShareMode(Mode): # Downloads self.downloads = Downloads(self.common) + self.downloads.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -96,7 +97,6 @@ class ShareMode(Mode): self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() # Primary action layout self.primary_action_layout.addWidget(self.filesize_warning) -- cgit v1.2.3-54-g00ecf From edbbe9377cac09d1c6063e23a09f68d925fa6acd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 13:24:44 -0700 Subject: Add a toggle downloads button to share mode, and add new toggle upload and download images --- onionshare_gui/share_mode/__init__.py | 15 +++++++++++++++ share/images/downloads_toggle.png | Bin 0 -> 380 bytes share/images/downloads_toggle_selected.png | Bin 0 -> 468 bytes share/images/uploads_toggle.png | Bin 0 -> 389 bytes share/images/uploads_toggle_selected.png | Bin 0 -> 473 bytes 5 files changed, 15 insertions(+) create mode 100644 share/images/downloads_toggle.png create mode 100644 share/images/downloads_toggle_selected.png create mode 100644 share/images/uploads_toggle.png create mode 100644 share/images/uploads_toggle_selected.png diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index c097d75a..7e444137 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -89,11 +89,20 @@ class ShareMode(Mode): self.update_downloads_completed() self.update_downloads_in_progress() + self.info_toggle_button = QtWidgets.QPushButton() + self.info_toggle_button.setDefault(False) + self.info_toggle_button.setFixedWidth(30) + self.info_toggle_button.setFixedHeight(30) + self.info_toggle_button.setFlat(True) + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.info_toggle_button.clicked.connect(self.toggle_downloads) + self.info_layout = QtWidgets.QHBoxLayout() self.info_layout.addWidget(self.info_label) self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_downloads_count) self.info_layout.addWidget(self.info_completed_downloads_count) + self.info_layout.addWidget(self.info_toggle_button) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -353,6 +362,12 @@ class ShareMode(Mode): self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) + def toggle_downloads(self): + """ + Toggle showing and hiding the Downloads widget + """ + pass + @staticmethod def _compute_total_size(filenames): total_size = 0 diff --git a/share/images/downloads_toggle.png b/share/images/downloads_toggle.png new file mode 100644 index 00000000..846ececb Binary files /dev/null and b/share/images/downloads_toggle.png differ diff --git a/share/images/downloads_toggle_selected.png b/share/images/downloads_toggle_selected.png new file mode 100644 index 00000000..127ce208 Binary files /dev/null and b/share/images/downloads_toggle_selected.png differ diff --git a/share/images/uploads_toggle.png b/share/images/uploads_toggle.png new file mode 100644 index 00000000..87303c9f Binary files /dev/null and b/share/images/uploads_toggle.png differ diff --git a/share/images/uploads_toggle_selected.png b/share/images/uploads_toggle_selected.png new file mode 100644 index 00000000..0ba52cff Binary files /dev/null and b/share/images/uploads_toggle_selected.png differ -- cgit v1.2.3-54-g00ecf From 4bec79f4944e1222d2f79c7a11668a80da497872 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 13:43:10 -0700 Subject: Toggle showing uploads and downloads for both share and receive modes --- onionshare_gui/mode.py | 1 + onionshare_gui/onionshare_gui.py | 7 +++++++ onionshare_gui/receive_mode/__init__.py | 28 +++++++++++++++++++++++++++- onionshare_gui/share_mode/__init__.py | 19 ++++++++++++++++--- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index d91b2e64..6da83318 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -36,6 +36,7 @@ class Mode(QtWidgets.QWidget): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) set_server_active = QtCore.pyqtSignal(bool) + adjust_size = QtCore.pyqtSignal() def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): super(Mode, self).__init__() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 79565edd..a95b75c9 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -132,6 +132,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.set_server_active.connect(self.set_server_active) + self.share_mode.adjust_size.connect(self.adjust_size) # Receive mode self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) @@ -146,6 +147,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.url_copied.connect(self.copy_url) self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.receive_mode.set_server_active.connect(self.set_server_active) + self.receive_mode.adjust_size.connect(self.adjust_size) self.update_mode_switcher() self.update_server_status_indicator() @@ -442,6 +444,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # Disable settings menu action when server is active self.settings_action.setEnabled(not active) + def adjust_size(self): + self.share_mode.adjustSize() + self.receive_mode.adjustSize() + self.adjustSize() + def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') try: diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 80bd9cad..efad618a 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -63,10 +63,19 @@ class ReceiveMode(Mode): self.update_uploads_completed() self.update_uploads_in_progress() + self.info_toggle_button = QtWidgets.QPushButton() + self.info_toggle_button.setDefault(False) + self.info_toggle_button.setFixedWidth(30) + self.info_toggle_button.setFixedHeight(30) + self.info_toggle_button.setFlat(True) + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.info_toggle_button.clicked.connect(self.toggle_uploads) + self.info_layout = QtWidgets.QHBoxLayout() self.info_layout.addStretch() self.info_layout.addWidget(self.info_in_progress_uploads_count) self.info_layout.addWidget(self.info_completed_uploads_count) + self.info_layout.addWidget(self.info_toggle_button) self.info_widget = QtWidgets.QWidget() self.info_widget.setLayout(self.info_layout) @@ -226,4 +235,21 @@ class ReceiveMode(Mode): self.info_widget.hide() # Resize window - self.adjustSize() + self.adjust_size.emit() + + def toggle_uploads(self): + """ + Toggle showing and hiding the Uploads widget + """ + self.common.log('ReceiveMode', 'toggle_uploads') + + if self.uploads.isVisible(): + self.uploads.hide() + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.info_toggle_button.setFlat(True) + else: + self.uploads.show() + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) + self.info_toggle_button.setFlat(False) + + self.adjust_size.emit() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 7e444137..cf33e8b1 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -226,7 +226,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('OnionShareGui', 'cancel_server: quitting compress thread') + self.common.log('ShareMode', 'cancel_server: quitting compress thread') self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -305,6 +305,8 @@ class ShareMode(Mode): self.info_widget.show() def update_primary_action(self): + self.common.log('ShareMode', 'update_primary_action') + # Show or hide primary action layout file_count = self.file_selection.file_list.count() if file_count > 0: @@ -328,7 +330,7 @@ class ShareMode(Mode): self.info_widget.hide() # Resize window - self.adjustSize() + self.adjust_size.emit() def reset_info_counters(self): """ @@ -366,7 +368,18 @@ class ShareMode(Mode): """ Toggle showing and hiding the Downloads widget """ - pass + self.common.log('ShareMode', 'toggle_downloads') + + if self.downloads.isVisible(): + self.downloads.hide() + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.info_toggle_button.setFlat(True) + else: + self.downloads.show() + self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) + self.info_toggle_button.setFlat(False) + + self.adjust_size.emit() @staticmethod def _compute_total_size(filenames): -- cgit v1.2.3-54-g00ecf From e29bb99f16f69be68dbee1667400a3a99451bd56 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 15:05:43 -0700 Subject: OnionShareGui.adjust_size now recursively runs adjustSize() on all widgets --- onionshare_gui/onionshare_gui.py | 31 +++++++++++++++++++++++++++++-- onionshare_gui/receive_mode/__init__.py | 10 +++++----- onionshare_gui/share_mode/__init__.py | 10 +++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index a95b75c9..5469c57c 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -445,8 +445,35 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_action.setEnabled(not active) def adjust_size(self): - self.share_mode.adjustSize() - self.receive_mode.adjustSize() + """ + Recursively adjust size on all widgets + """ + # Recursively adjust sizes for the modes + def adjust_size_layout(layout): + count = layout.count() + for i in range(count): + item = layout.itemAt(i) + if item: + child_widget = item.widget() + if child_widget: + adjust_size_widget(child_widget) + child_layout = item.layout() + if child_layout: + adjust_size_layout(child_layout) + + def adjust_size_widget(widget): + layout = widget.layout() + if layout: + adjust_size_layout(layout) + # Processing Qt events before adjusting size makes each .adjustSize() actually count + self.qtapp.processEvents() + widget.adjustSize() + + # Adjust sizes of each mode + for mode in [self.share_mode, self.receive_mode]: + adjust_size_widget(mode) + + # Adjust window size self.adjustSize() def closeEvent(self, e): diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index efad618a..dab37ef2 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -94,11 +94,11 @@ class ReceiveMode(Mode): self.main_layout.addStretch() self.main_layout.addWidget(self.min_width_widget) - # Layout - self.layout = QtWidgets.QHBoxLayout() - self.layout.addLayout(self.main_layout) - self.layout.addWidget(self.uploads) - self.setLayout(self.layout) + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.uploads) + self.setLayout(self.wrapper_layout) def get_stop_server_shutdown_timeout_text(self): """ diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index cf33e8b1..c0cb6d39 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -122,11 +122,11 @@ class ShareMode(Mode): self.main_layout.addWidget(self.primary_action) self.main_layout.addWidget(self.min_width_widget) - # Layout - self.layout = QtWidgets.QHBoxLayout() - self.layout.addLayout(self.main_layout) - self.layout.addWidget(self.downloads) - self.setLayout(self.layout) + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.downloads) + self.setLayout(self.wrapper_layout) # Always start with focus on file selection self.file_selection.setFocus() -- cgit v1.2.3-54-g00ecf From cddc8c06d4ebfbec16479f2945343b36c6386ed9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 15:30:37 -0700 Subject: Modes now get to choose a new minimum window width when resizing --- onionshare_gui/mode.py | 21 ++++++++++++++++++++- onionshare_gui/onionshare_gui.py | 14 ++++++++++---- onionshare_gui/receive_mode/__init__.py | 10 ++++++++-- onionshare_gui/share_mode/__init__.py | 10 ++++++++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 6da83318..bd247568 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -36,7 +36,7 @@ class Mode(QtWidgets.QWidget): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) set_server_active = QtCore.pyqtSignal(bool) - adjust_size = QtCore.pyqtSignal() + adjust_size = QtCore.pyqtSignal(int) def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): super(Mode, self).__init__() @@ -332,3 +332,22 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_FINISHED event. """ pass + + def resize_window(self): + """ + We call this to force the OnionShare window to resize itself to be smaller. + For this to do anything, the Mode needs to override it and call: + + self.adjust_size.emit(min_width) + + It can calculate min_width (the new minimum window width) based on what + widgets are visible. + """ + pass + + def show(self): + """ + Always resize the window after showing this Mode widget. + """ + super(Mode, self).show() + self.resize_window() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 5469c57c..e2d95dab 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -168,6 +168,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() + # Adjust window size, to start with a minimum window width + self.adjust_size(450) + # The server isn't active yet self.set_server_active(False) @@ -444,10 +447,14 @@ class OnionShareGui(QtWidgets.QMainWindow): # Disable settings menu action when server is active self.settings_action.setEnabled(not active) - def adjust_size(self): + def adjust_size(self, min_width): """ - Recursively adjust size on all widgets + Recursively adjust size on all widgets. min_width is the new minimum width + of the window. """ + self.common.log("OnionShareGui", "adjust_size", "min_width={}".format(min_width)) + self.setMinimumWidth(min_width) + # Recursively adjust sizes for the modes def adjust_size_layout(layout): count = layout.count() @@ -465,8 +472,6 @@ class OnionShareGui(QtWidgets.QMainWindow): layout = widget.layout() if layout: adjust_size_layout(layout) - # Processing Qt events before adjusting size makes each .adjustSize() actually count - self.qtapp.processEvents() widget.adjustSize() # Adjust sizes of each mode @@ -474,6 +479,7 @@ class OnionShareGui(QtWidgets.QMainWindow): adjust_size_widget(mode) # Adjust window size + self.qtapp.processEvents() self.adjustSize() def closeEvent(self, e): diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index dab37ef2..e30ef519 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -235,7 +235,7 @@ class ReceiveMode(Mode): self.info_widget.hide() # Resize window - self.adjust_size.emit() + self.resize_window() def toggle_uploads(self): """ @@ -252,4 +252,10 @@ class ReceiveMode(Mode): self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) self.info_toggle_button.setFlat(False) - self.adjust_size.emit() + self.resize_window() + + def resize_window(self): + min_width = 450 + if self.uploads.isVisible(): + min_width += 300 + self.adjust_size.emit(min_width) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index c0cb6d39..cc0a9f32 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -330,7 +330,7 @@ class ShareMode(Mode): self.info_widget.hide() # Resize window - self.adjust_size.emit() + self.resize_window() def reset_info_counters(self): """ @@ -379,7 +379,13 @@ class ShareMode(Mode): self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) self.info_toggle_button.setFlat(False) - self.adjust_size.emit() + self.resize_window() + + def resize_window(self): + min_width = 450 + if self.downloads.isVisible(): + min_width += 300 + self.adjust_size.emit(min_width) @staticmethod def _compute_total_size(filenames): -- cgit v1.2.3-54-g00ecf From f056ce576eee6ff5ea97b32a3642272052733a03 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 15:47:49 -0700 Subject: Refactor share mode info widget into its own file and custom class, and run .show_more() and .show_less() instead of .show() and .hide() --- onionshare_gui/share_mode/__init__.py | 100 +++++----------------------- onionshare_gui/share_mode/info.py | 120 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 83 deletions(-) create mode 100644 onionshare_gui/share_mode/info.py diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index cc0a9f32..a8828497 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -28,6 +28,7 @@ from onionshare.web import Web from .file_selection import FileSelection from .downloads import Downloads from .threads import CompressThread +from .info import Info from ..mode import Mode from ..widgets import Alert @@ -77,35 +78,7 @@ class ShareMode(Mode): self.downloads_completed = 0 # Information about share, and show downloads button - self.info_label = QtWidgets.QLabel() - self.info_label.setStyleSheet(self.common.css['mode_info_label']) - - self.info_in_progress_downloads_count = QtWidgets.QLabel() - self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.info_completed_downloads_count = QtWidgets.QLabel() - self.info_completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.update_downloads_completed() - self.update_downloads_in_progress() - - self.info_toggle_button = QtWidgets.QPushButton() - self.info_toggle_button.setDefault(False) - self.info_toggle_button.setFixedWidth(30) - self.info_toggle_button.setFixedHeight(30) - self.info_toggle_button.setFlat(True) - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.info_toggle_button.clicked.connect(self.toggle_downloads) - - self.info_layout = QtWidgets.QHBoxLayout() - self.info_layout.addWidget(self.info_label) - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_downloads_count) - self.info_layout.addWidget(self.info_completed_downloads_count) - self.info_layout.addWidget(self.info_toggle_button) - - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) + self.info = Info(self.common, self) # Primary action layout self.primary_action_layout.addWidget(self.filesize_warning) @@ -117,7 +90,7 @@ class ShareMode(Mode): # Main layout self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info_widget) + self.main_layout.addWidget(self.info) self.main_layout.addLayout(self.file_selection) self.main_layout.addWidget(self.primary_action) self.main_layout.addWidget(self.min_width_widget) @@ -218,7 +191,7 @@ class ShareMode(Mode): self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 - self.update_downloads_in_progress() + self.info.update_downloads_in_progress() self.file_selection.file_list.adjustSize() def cancel_server_custom(self): @@ -234,7 +207,7 @@ class ShareMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info_widget.hide() + self.info.show_less() def handle_request_load(self, event): """ @@ -252,7 +225,7 @@ class ShareMode(Mode): filesize = self.web.share_mode.download_filesize self.downloads.add(event["data"]["id"], filesize) self.downloads_in_progress += 1 - self.update_downloads_in_progress() + self.info.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) @@ -268,10 +241,10 @@ class ShareMode(Mode): # Update the total 'completed downloads' info self.downloads_completed += 1 - self.update_downloads_completed() + self.info.update_downloads_completed() # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.update_downloads_in_progress() + self.info.update_downloads_in_progress() # Close on finish? if self.common.settings.get('close_after_first_download'): @@ -282,7 +255,7 @@ class ShareMode(Mode): if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel(event["data"]["id"]) self.downloads_in_progress = 0 - self.update_downloads_in_progress() + self.info.update_downloads_in_progress() def handle_request_canceled(self, event): """ @@ -292,7 +265,7 @@ class ShareMode(Mode): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.update_downloads_in_progress() + self.info.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) def on_reload_settings(self): @@ -302,7 +275,7 @@ class ShareMode(Mode): """ if self.server_status.file_selection.get_num_files() > 0: self.primary_action.show() - self.info_widget.show() + self.info.show_more() def update_primary_action(self): self.common.log('ShareMode', 'update_primary_action') @@ -311,7 +284,7 @@ class ShareMode(Mode): file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() - self.info_widget.show() + self.info.show_more() # Update the file count in the info label total_size_bytes = 0 @@ -321,13 +294,13 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) + self.info.update_label(strings._('gui_file_info', True).format(file_count, total_size_readable)) else: - self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + self.info.update_label(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) else: self.primary_action.hide() - self.info_widget.hide() + self.info.show_less() # Resize window self.resize_window() @@ -338,49 +311,10 @@ class ShareMode(Mode): """ self.downloads_completed = 0 self.downloads_in_progress = 0 - self.update_downloads_completed() - self.update_downloads_in_progress() + self.info.update_downloads_completed() + self.info.update_downloads_in_progress() self.downloads.reset() - def update_downloads_completed(self): - """ - Update the 'Downloads completed' info widget. - """ - if self.downloads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.info_completed_downloads_count.setText(' {1:d}'.format(image, self.downloads_completed)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed)) - - def update_downloads_in_progress(self): - """ - Update the 'Downloads in progress' info widget. - """ - if self.downloads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) - - def toggle_downloads(self): - """ - Toggle showing and hiding the Downloads widget - """ - self.common.log('ShareMode', 'toggle_downloads') - - if self.downloads.isVisible(): - self.downloads.hide() - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.info_toggle_button.setFlat(True) - else: - self.downloads.show() - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) - self.info_toggle_button.setFlat(False) - - self.resize_window() - def resize_window(self): min_width = 450 if self.downloads.isVisible(): diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py new file mode 100644 index 00000000..548d70e3 --- /dev/null +++ b/onionshare_gui/share_mode/info.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class Info(QtWidgets.QWidget): + """ + Share mode information widget + """ + def __init__(self, common, share_mode): + super(Info, self).__init__() + self.common = common + self.share_mode = share_mode + + # Label + self.label = QtWidgets.QLabel() + self.label.setStyleSheet(self.common.css['mode_info_label']) + + # In prorgess and completed labels + self.in_progress_downloads_count = QtWidgets.QLabel() + self.in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) + self.completed_downloads_count = QtWidgets.QLabel() + self.completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) + + # Toggle button + self.toggle_button = QtWidgets.QPushButton() + self.toggle_button.setDefault(False) + self.toggle_button.setFixedWidth(30) + self.toggle_button.setFixedHeight(30) + self.toggle_button.setFlat(True) + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.toggle_button.clicked.connect(self.toggle_downloads) + + # Info layout + layout = QtWidgets.QHBoxLayout() + layout.addWidget(self.label) + layout.addStretch() + layout.addWidget(self.in_progress_downloads_count) + layout.addWidget(self.completed_downloads_count) + layout.addWidget(self.toggle_button) + self.setLayout(layout) + + self.update_downloads_completed() + self.update_downloads_in_progress() + + def update_label(self, s): + """ + Updates the text of the label. + """ + self.label.setText(s) + + def update_downloads_completed(self): + """ + Update the 'Downloads completed' info widget. + """ + if self.share_mode.downloads_completed == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_completed)) + self.completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.share_mode.downloads_completed)) + + def update_downloads_in_progress(self): + """ + Update the 'Downloads in progress' info widget. + """ + if self.share_mode.downloads_in_progress == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_in_progress)) + self.in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.share_mode.downloads_in_progress)) + + def toggle_downloads(self): + """ + Toggle showing and hiding the Downloads widget + """ + self.common.log('ShareMode', 'toggle_downloads') + + if self.share_mode.downloads.isVisible(): + self.share_mode.downloads.hide() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.toggle_button.setFlat(True) + else: + self.share_mode.downloads.show() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) + self.toggle_button.setFlat(False) + + self.share_mode.resize_window() + + def show_less(self): + """ + Remove clutter widgets that aren't necessary. + """ + self.label.hide() + + def show_more(self): + """ + Show all widgets. + """ + self.label.show() -- cgit v1.2.3-54-g00ecf From 35065106efa934fac95991d1b593704e47bb12b5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 16:00:22 -0700 Subject: Refactor receive mode into using an info widget too --- onionshare_gui/onionshare_gui.py | 1 - onionshare_gui/receive_mode/__init__.py | 88 ++++---------------------- onionshare_gui/receive_mode/info.py | 109 ++++++++++++++++++++++++++++++++ onionshare_gui/share_mode/__init__.py | 4 +- onionshare_gui/share_mode/info.py | 16 +++-- 5 files changed, 133 insertions(+), 85 deletions(-) create mode 100644 onionshare_gui/receive_mode/info.py diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index e2d95dab..7cc368b8 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -452,7 +452,6 @@ class OnionShareGui(QtWidgets.QMainWindow): Recursively adjust size on all widgets. min_width is the new minimum width of the window. """ - self.common.log("OnionShareGui", "adjust_size", "min_width={}".format(min_width)) self.setMinimumWidth(min_width) # Recursively adjust sizes for the modes diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index e30ef519..83113805 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -23,6 +23,7 @@ from onionshare import strings from onionshare.web import Web from .uploads import Uploads +from .info import ReceiveModeInfo from ..mode import Mode class ReceiveMode(Mode): @@ -54,32 +55,8 @@ class ReceiveMode(Mode): self.new_upload = False # For scrolling to the bottom of the uploads list # Information about share, and show uploads button - self.info_in_progress_uploads_count = QtWidgets.QLabel() - self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.info_completed_uploads_count = QtWidgets.QLabel() - self.info_completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - self.update_uploads_completed() - self.update_uploads_in_progress() - - self.info_toggle_button = QtWidgets.QPushButton() - self.info_toggle_button.setDefault(False) - self.info_toggle_button.setFixedWidth(30) - self.info_toggle_button.setFixedHeight(30) - self.info_toggle_button.setFlat(True) - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.info_toggle_button.clicked.connect(self.toggle_uploads) - - self.info_layout = QtWidgets.QHBoxLayout() - self.info_layout.addStretch() - self.info_layout.addWidget(self.info_in_progress_uploads_count) - self.info_layout.addWidget(self.info_completed_uploads_count) - self.info_layout.addWidget(self.info_toggle_button) - - self.info_widget = QtWidgets.QWidget() - self.info_widget.setLayout(self.info_layout) - self.info_widget.hide() + self.info = ReceiveModeInfo(self.common, self) + self.info.show_less() # Receive mode info self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) @@ -88,7 +65,7 @@ class ReceiveMode(Mode): # Main layout self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info_widget) + self.main_layout.addWidget(self.info) self.main_layout.addWidget(self.receive_info) self.main_layout.addWidget(self.primary_action) self.main_layout.addStretch() @@ -137,7 +114,7 @@ class ReceiveMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info_widget.hide() + self.info.show_less() def handle_request_load(self, event): """ @@ -151,7 +128,7 @@ class ReceiveMode(Mode): """ self.uploads.add(event["data"]["id"], event["data"]["content_length"]) self.uploads_in_progress += 1 - self.update_uploads_in_progress() + self.info.update_uploads_in_progress() self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) @@ -181,17 +158,17 @@ class ReceiveMode(Mode): self.uploads.finished(event["data"]["id"]) # Update the total 'completed uploads' info self.uploads_completed += 1 - self.update_uploads_completed() + self.info.update_uploads_completed() # Update the 'in progress uploads' info self.uploads_in_progress -= 1 - self.update_uploads_in_progress() + self.info.update_uploads_in_progress() def on_reload_settings(self): """ We should be ok to re-enable the 'Start Receive Mode' button now. """ self.primary_action.show() - self.info_widget.show() + self.info.show_more() def reset_info_counters(self): """ @@ -199,61 +176,22 @@ class ReceiveMode(Mode): """ self.uploads_completed = 0 self.uploads_in_progress = 0 - self.update_uploads_completed() - self.update_uploads_in_progress() + self.info.update_uploads_completed() + self.info.update_uploads_in_progress() self.uploads.reset() - def update_uploads_completed(self): - """ - Update the 'Uploads completed' info widget. - """ - if self.uploads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) - self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed)) - - def update_uploads_in_progress(self): - """ - Update the 'Uploads in progress' info widget. - """ - if self.uploads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) - self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) - def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') # Show the info widget when the server is active if self.server_status.status == self.server_status.STATUS_STARTED: - self.info_widget.show() + self.info.show_more() else: - self.info_widget.hide() + self.info.show_less() # Resize window self.resize_window() - def toggle_uploads(self): - """ - Toggle showing and hiding the Uploads widget - """ - self.common.log('ReceiveMode', 'toggle_uploads') - - if self.uploads.isVisible(): - self.uploads.hide() - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.info_toggle_button.setFlat(True) - else: - self.uploads.show() - self.info_toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) - self.info_toggle_button.setFlat(False) - - self.resize_window() - def resize_window(self): min_width = 450 if self.uploads.isVisible(): diff --git a/onionshare_gui/receive_mode/info.py b/onionshare_gui/receive_mode/info.py new file mode 100644 index 00000000..0f5bc298 --- /dev/null +++ b/onionshare_gui/receive_mode/info.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class ReceiveModeInfo(QtWidgets.QWidget): + """ + Receive mode information widget + """ + def __init__(self, common, receive_mode): + super(ReceiveModeInfo, self).__init__() + self.common = common + self.receive_mode = receive_mode + + # In progress and completed labels + self.in_progress_uploads_count = QtWidgets.QLabel() + self.in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) + self.completed_uploads_count = QtWidgets.QLabel() + self.completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) + + # Toggle button + self.toggle_button = QtWidgets.QPushButton() + self.toggle_button.setDefault(False) + self.toggle_button.setFixedWidth(30) + self.toggle_button.setFixedHeight(30) + self.toggle_button.setFlat(True) + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.toggle_button.clicked.connect(self.toggle_uploads) + + # Layout + layout = QtWidgets.QHBoxLayout() + layout.addStretch() + layout.addWidget(self.in_progress_uploads_count) + layout.addWidget(self.completed_uploads_count) + layout.addWidget(self.toggle_button) + self.setLayout(layout) + + self.update_uploads_completed() + self.update_uploads_in_progress() + + def update_uploads_completed(self): + """ + Update the 'Uploads completed' info widget. + """ + if self.receive_mode.uploads_completed == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_completed)) + self.completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.receive_mode.uploads_completed)) + + def update_uploads_in_progress(self): + """ + Update the 'Uploads in progress' info widget. + """ + if self.receive_mode.uploads_in_progress == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_in_progress)) + self.in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.receive_mode.uploads_in_progress)) + + def toggle_uploads(self): + """ + Toggle showing and hiding the Uploads widget + """ + self.common.log('ReceiveModeInfo', 'toggle_uploads') + + if self.receive_mode.uploads.isVisible(): + self.receive_mode.uploads.hide() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.toggle_button.setFlat(True) + else: + self.receive_mode.uploads.show() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) + self.toggle_button.setFlat(False) + + self.receive_mode.resize_window() + + def show_less(self): + """ + Remove clutter widgets that aren't necessary. + """ + pass + + def show_more(self): + """ + Show all widgets. + """ + pass diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index a8828497..0504b529 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -28,7 +28,7 @@ from onionshare.web import Web from .file_selection import FileSelection from .downloads import Downloads from .threads import CompressThread -from .info import Info +from .info import ShareModeInfo from ..mode import Mode from ..widgets import Alert @@ -78,7 +78,7 @@ class ShareMode(Mode): self.downloads_completed = 0 # Information about share, and show downloads button - self.info = Info(self.common, self) + self.info = ShareModeInfo(self.common, self) # Primary action layout self.primary_action_layout.addWidget(self.filesize_warning) diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py index 548d70e3..3ee12a95 100644 --- a/onionshare_gui/share_mode/info.py +++ b/onionshare_gui/share_mode/info.py @@ -22,20 +22,21 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -class Info(QtWidgets.QWidget): +class ShareModeInfo(QtWidgets.QWidget): """ Share mode information widget """ def __init__(self, common, share_mode): - super(Info, self).__init__() + super(ShareModeInfo, self).__init__() self.common = common self.share_mode = share_mode # Label + self.label_text = "" self.label = QtWidgets.QLabel() self.label.setStyleSheet(self.common.css['mode_info_label']) - # In prorgess and completed labels + # In progress and completed labels self.in_progress_downloads_count = QtWidgets.QLabel() self.in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) self.completed_downloads_count = QtWidgets.QLabel() @@ -66,7 +67,8 @@ class Info(QtWidgets.QWidget): """ Updates the text of the label. """ - self.label.setText(s) + self.label_text = s + self.label.setText(self.label_text) def update_downloads_completed(self): """ @@ -94,7 +96,7 @@ class Info(QtWidgets.QWidget): """ Toggle showing and hiding the Downloads widget """ - self.common.log('ShareMode', 'toggle_downloads') + self.common.log('ShareModeInfo', 'toggle_downloads') if self.share_mode.downloads.isVisible(): self.share_mode.downloads.hide() @@ -111,10 +113,10 @@ class Info(QtWidgets.QWidget): """ Remove clutter widgets that aren't necessary. """ - self.label.hide() + self.label.setText("") def show_more(self): """ Show all widgets. """ - self.label.show() + self.label.setText(self.label_text) -- cgit v1.2.3-54-g00ecf From 08ac4137c7b70e20bc28016eddbe3197bceb3a6b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 16:06:14 -0700 Subject: Process Qt events once more, to prevent weird size issues before adjusting size --- onionshare_gui/mode.py | 1 + onionshare_gui/onionshare_gui.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index bd247568..0fba029b 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -350,4 +350,5 @@ class Mode(QtWidgets.QWidget): Always resize the window after showing this Mode widget. """ super(Mode, self).show() + self.qtapp.processEvents() self.resize_window() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 7cc368b8..51190ea3 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -454,7 +454,6 @@ class OnionShareGui(QtWidgets.QMainWindow): """ self.setMinimumWidth(min_width) - # Recursively adjust sizes for the modes def adjust_size_layout(layout): count = layout.count() for i in range(count): -- cgit v1.2.3-54-g00ecf From c0e6968b2bcf8f7c490db375980fbef976d15e7d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 17:01:48 -0700 Subject: Attempting to redesign Downloads --- onionshare/common.py | 15 +++++++ onionshare_gui/share_mode/downloads.py | 74 +++++++++++++++++++++++---------- share/images/downloads.png | Bin 0 -> 2120 bytes share/images/downloads_transparent.png | Bin 0 -> 2138 bytes share/images/uploads.png | Bin 0 -> 2076 bytes share/images/uploads_transparent.png | Bin 0 -> 2096 bytes share/locale/en.json | 4 +- 7 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 share/images/downloads.png create mode 100644 share/images/downloads_transparent.png create mode 100644 share/images/uploads.png create mode 100644 share/images/uploads_transparent.png diff --git a/onionshare/common.py b/onionshare/common.py index 28b282c2..78b6e2d7 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -248,6 +248,15 @@ class Common(object): border-radius: 5px; }""", + 'downloads_uploads': """ + background-color: #ffffff; + """, + + 'downloads_uploads_empty_text': """ + QLabel { + color: #999999; + }""", + 'downloads_uploads_label': """ QLabel { font-weight: bold; @@ -255,6 +264,12 @@ class Common(object): text-align: center; }""", + 'downloads_uploads_clear': """ + QPushButton { + color: #3f7fcf; + } + """, + 'downloads_uploads_progress_bar': """ QProgressBar { border: 1px solid #4e064f; diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index a34796f1..3da88bc4 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -89,33 +89,64 @@ class Downloads(QtWidgets.QScrollArea): self.downloads = {} - self.setWindowTitle(strings._('gui_downloads', True)) - 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.setStyleSheet(self.common.css['downloads_uploads']) + + # Scroll bar self.vbar = self.verticalScrollBar() self.vbar.rangeChanged.connect(self.resizeScroll) + # When there are no downloads + empty_image = QtWidgets.QLabel() + empty_image.setAlignment(QtCore.Qt.AlignCenter) + empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png')))) + empty_text = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + empty_text.setAlignment(QtCore.Qt.AlignCenter) + empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(empty_image) + empty_layout.addWidget(empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setLayout(empty_layout) + + # When there are downloads downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - self.clear_history_button.clicked.connect(self.reset) - self.clear_history_button.hide() + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + download_header = QtWidgets.QHBoxLayout() + download_header.addWidget(downloads_label) + download_header.addStretch() + download_header.addWidget(clear_button) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(download_header) self.downloads_layout = QtWidgets.QVBoxLayout() - widget = QtWidgets.QWidget() + # Layout + self.widget = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout() - layout.addWidget(downloads_label) - layout.addWidget(self.no_downloads_label) - layout.addWidget(self.clear_history_button) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) layout.addLayout(self.downloads_layout) layout.addStretch() - widget.setLayout(layout) - self.setWidget(widget) + self.widget.setLayout(layout) + self.setWidget(self.widget) + + # Reset once at the beginning + self.reset() + + def resizeEvent(self, event): + """ + When the widget resizes, resize the inner widget to match + """ + #self.empty.resize(self.width()-2, self.width()-2) + pass def resizeScroll(self, minimum, maximum): """ @@ -127,10 +158,9 @@ class Downloads(QtWidgets.QScrollArea): """ 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() + # Hide empty, show not empty + self.empty.hide() + self.not_empty.show() # Add it to the list download = Download(self.common, download_id, total_bytes) @@ -158,6 +188,6 @@ class Downloads(QtWidgets.QScrollArea): download.progress_bar.close() self.downloads = {} - self.no_downloads_label.show() - self.clear_history_button.hide() - self.resize(self.sizeHint()) + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() diff --git a/share/images/downloads.png b/share/images/downloads.png new file mode 100644 index 00000000..ad879b6e Binary files /dev/null and b/share/images/downloads.png differ diff --git a/share/images/downloads_transparent.png b/share/images/downloads_transparent.png new file mode 100644 index 00000000..99207097 Binary files /dev/null and b/share/images/downloads_transparent.png differ diff --git a/share/images/uploads.png b/share/images/uploads.png new file mode 100644 index 00000000..cd9bd98e Binary files /dev/null and b/share/images/uploads.png differ diff --git a/share/images/uploads_transparent.png b/share/images/uploads_transparent.png new file mode 100644 index 00000000..3648c3fb Binary files /dev/null and b/share/images/uploads_transparent.png differ diff --git a/share/locale/en.json b/share/locale/en.json index 0f0f0cf4..5821eea2 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -46,7 +46,7 @@ "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", "gui_downloads": "Download History", - "gui_no_downloads": "No downloads yet.", + "gui_no_downloads": "No Downloads Yet", "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare Address", "gui_copied_url": "OnionShare address copied to clipboard", @@ -176,7 +176,7 @@ "systray_upload_page_loaded_message": "A user loaded the upload page", "gui_uploads": "Upload History", "gui_no_uploads": "No uploads yet.", - "gui_clear_history": "Clear history", + "gui_clear_history": "Clear All", "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", -- cgit v1.2.3-54-g00ecf From a5b549770aca8ae46413426603025ab871472611 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 18:30:32 -0700 Subject: Got empty Downloads looking good --- onionshare/common.py | 13 +++++++++++-- onionshare_gui/share_mode/downloads.py | 31 ++++++++++++++++--------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 78b6e2d7..3489c3e6 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -248,8 +248,15 @@ class Common(object): border-radius: 5px; }""", - 'downloads_uploads': """ - background-color: #ffffff; + 'downloads_uploads_empty': """ + QWidget { + background-color: #ffffff; + border: 1px solid #999999; + } + QWidget QLabel { + background-color: none; + border: 0px; + } """, 'downloads_uploads_empty_text': """ @@ -262,6 +269,8 @@ class Common(object): font-weight: bold; font-size 14px; text-align: center; + background-color: none; + border: none; }""", 'downloads_uploads_clear': """ diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 3da88bc4..fcf8bc8e 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -78,7 +78,7 @@ class Download(object): self.started) -class Downloads(QtWidgets.QScrollArea): +class Downloads(QtWidgets.QWidget): """ The downloads chunk of the GUI. This lists all of the active download progress bars. @@ -90,11 +90,6 @@ class Downloads(QtWidgets.QScrollArea): self.downloads = {} self.setMinimumWidth(350) - self.setStyleSheet(self.common.css['downloads_uploads']) - - # Scroll bar - self.vbar = self.verticalScrollBar() - self.vbar.rangeChanged.connect(self.resizeScroll) # When there are no downloads empty_image = QtWidgets.QLabel() @@ -109,6 +104,7 @@ class Downloads(QtWidgets.QScrollArea): empty_layout.addWidget(empty_text) empty_layout.addStretch() self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) self.empty.setLayout(empty_layout) # When there are downloads @@ -122,25 +118,29 @@ class Downloads(QtWidgets.QScrollArea): download_header.addWidget(downloads_label) download_header.addStretch() download_header.addWidget(clear_button) - self.not_empty = QtWidgets.QWidget() - self.not_empty.setLayout(download_header) - self.downloads_layout = QtWidgets.QVBoxLayout() + not_empty_layout = QtWidgets.QVBoxLayout() + not_empty_layout.addLayout(download_header) + not_empty_layout.addLayout(self.downloads_layout) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(not_empty_layout) # Layout - self.widget = QtWidgets.QWidget() layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.empty) layout.addWidget(self.not_empty) - layout.addLayout(self.downloads_layout) - layout.addStretch() - self.widget.setLayout(layout) - self.setWidget(self.widget) + self.setLayout(layout) # Reset once at the beginning self.reset() + """ + # Scroll bar + self.vbar = self.verticalScrollBar() + self.vbar.rangeChanged.connect(self.resizeScroll) + """ + def resizeEvent(self, event): """ When the widget resizes, resize the inner widget to match @@ -152,7 +152,8 @@ class Downloads(QtWidgets.QScrollArea): """ Scroll to the bottom of the window when the range changes. """ - self.vbar.setValue(maximum) + pass + #self.vbar.setValue(maximum) def add(self, download_id, total_bytes): """ -- cgit v1.2.3-54-g00ecf From 71149c2937ba52429ef4360caaff28fc547e8380 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 18:48:12 -0700 Subject: Refactor Downloads to use an internal QListWidget to list the progess bars --- onionshare_gui/share_mode/downloads.py | 83 ++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index fcf8bc8e..855827b1 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -78,6 +78,59 @@ class Download(object): self.started) +class DownloadList(QtWidgets.QListWidget): + """ + List of download progess bars. + """ + def __init__(self, common): + super(DownloadList, self).__init__() + self.common = common + + self.downloads = {} + + self.setMinimumHeight(205) + self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + + def add(self, download_id, total_bytes): + """ + Add a new download progress bar. + """ + download = Download(self.common, download_id, total_bytes) + self.downloads[download_id] = download + + item = QtWidgets.QListWidgetItem() + self.addItem(item) + self.setItemWidget(item, 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 + """ + # Remove all items from list + while True: + item = self.takeItem(0) + if not item: + break + + # Close all progress bars + for download in self.downloads.values(): + download.progress_bar.close() + self.downloads = {} + + class Downloads(QtWidgets.QWidget): """ The downloads chunk of the GUI. This lists all of the active download @@ -87,8 +140,6 @@ class Downloads(QtWidgets.QWidget): super(Downloads, self).__init__() self.common = common - self.downloads = {} - self.setMinimumWidth(350) # When there are no downloads @@ -108,6 +159,9 @@ class Downloads(QtWidgets.QWidget): self.empty.setLayout(empty_layout) # When there are downloads + self.download_list = DownloadList(self.common) + + # Download header downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) @@ -118,10 +172,11 @@ class Downloads(QtWidgets.QWidget): download_header.addWidget(downloads_label) download_header.addStretch() download_header.addWidget(clear_button) - self.downloads_layout = QtWidgets.QVBoxLayout() + + # Download layout not_empty_layout = QtWidgets.QVBoxLayout() not_empty_layout.addLayout(download_header) - not_empty_layout.addLayout(self.downloads_layout) + not_empty_layout.addWidget(self.download_list) self.not_empty = QtWidgets.QWidget() self.not_empty.setLayout(not_empty_layout) @@ -141,13 +196,6 @@ class Downloads(QtWidgets.QWidget): self.vbar.rangeChanged.connect(self.resizeScroll) """ - def resizeEvent(self, event): - """ - When the widget resizes, resize the inner widget to match - """ - #self.empty.resize(self.width()-2, self.width()-2) - pass - def resizeScroll(self, minimum, maximum): """ Scroll to the bottom of the window when the range changes. @@ -164,30 +212,25 @@ class Downloads(QtWidgets.QWidget): self.not_empty.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) + self.download_list.add(download_id, total_bytes) def update(self, download_id, downloaded_bytes): """ Update the progress of a download progress bar. """ - self.downloads[download_id].update(downloaded_bytes) + self.download_list.update(download_id, downloaded_bytes) def cancel(self, download_id): """ Update a download progress bar to show that it has been canceled. """ - self.downloads[download_id].cancel() + self.download_list.cancel(download_id) 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.download_list.reset() # Hide not empty, show empty self.not_empty.hide() -- cgit v1.2.3-54-g00ecf From 9a05eef49495f7100f2103a3fcbfae6b0928b748 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 19:05:26 -0700 Subject: Slightly improve Downloads progress bar style, but still needs spacing --- onionshare/common.py | 2 +- onionshare_gui/share_mode/downloads.py | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 3489c3e6..6662ca4e 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -285,7 +285,7 @@ class Common(object): background-color: #ffffff !important; text-align: center; color: #9b9b9b; - font-size: 12px; + font-size: 14px; } QProgressBar::chunk { background-color: #4e064f; diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 855827b1..73e8faf5 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -190,19 +190,6 @@ class Downloads(QtWidgets.QWidget): # Reset once at the beginning self.reset() - """ - # Scroll bar - self.vbar = self.verticalScrollBar() - self.vbar.rangeChanged.connect(self.resizeScroll) - """ - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - pass - #self.vbar.setValue(maximum) - def add(self, download_id, total_bytes): """ Add a new download progress bar. -- cgit v1.2.3-54-g00ecf From e87263353f2cfae32eb965d2e5b8c1cdfc898e20 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 19:54:46 -0700 Subject: Added an indicator count for share mode --- onionshare/common.py | 10 ++++++++++ onionshare_gui/share_mode/__init__.py | 1 + onionshare_gui/share_mode/info.py | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/onionshare/common.py b/onionshare/common.py index 6662ca4e..43a6ac43 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -279,6 +279,16 @@ class Common(object): } """, + 'download_uploads_indicator': """ + QLabel { + color: #ffffff; + background-color: #f44449; + font-weight: bold; + padding: 5px; + border-radius: 5px; + font-size: 10px; + }""", + 'downloads_uploads_progress_bar': """ QProgressBar { border: 1px solid #4e064f; diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 0504b529..58801c45 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -224,6 +224,7 @@ class ShareMode(Mode): else: filesize = self.web.share_mode.download_filesize self.downloads.add(event["data"]["id"], filesize) + self.info.update_indicator(True) self.downloads_in_progress += 1 self.info.update_downloads_in_progress() diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py index 3ee12a95..a191933d 100644 --- a/onionshare_gui/share_mode/info.py +++ b/onionshare_gui/share_mode/info.py @@ -51,12 +51,19 @@ class ShareModeInfo(QtWidgets.QWidget): self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) self.toggle_button.clicked.connect(self.toggle_downloads) + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel() + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + # Info layout layout = QtWidgets.QHBoxLayout() layout.addWidget(self.label) layout.addStretch() layout.addWidget(self.in_progress_downloads_count) layout.addWidget(self.completed_downloads_count) + layout.addWidget(self.indicator_label) layout.addWidget(self.toggle_button) self.setLayout(layout) @@ -70,6 +77,21 @@ class ShareModeInfo(QtWidgets.QWidget): self.label_text = s self.label.setText(self.label_text) + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Downloads is hidden. + """ + if increment and not self.share_mode.downloads.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + self.indicator_label.show() + def update_downloads_completed(self): """ Update the 'Downloads completed' info widget. @@ -107,6 +129,10 @@ class ShareModeInfo(QtWidgets.QWidget): self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) self.toggle_button.setFlat(False) + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + self.share_mode.resize_window() def show_less(self): -- cgit v1.2.3-54-g00ecf From 709eeeac5f5805e63279ec68eeee5fde31ef345d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Sep 2018 22:03:48 -0700 Subject: Starting to implement the new Uploads UI, but not done --- onionshare_gui/receive_mode/__init__.py | 1 + onionshare_gui/receive_mode/info.py | 26 +++++ onionshare_gui/receive_mode/uploads.py | 183 +++++++++++++++++++++----------- onionshare_gui/share_mode/downloads.py | 2 + onionshare_gui/share_mode/info.py | 2 +- share/locale/en.json | 2 +- 6 files changed, 153 insertions(+), 63 deletions(-) diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 83113805..2f61b2ca 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -127,6 +127,7 @@ class ReceiveMode(Mode): Handle REQUEST_STARTED event. """ self.uploads.add(event["data"]["id"], event["data"]["content_length"]) + self.info.update_indicator(True) self.uploads_in_progress += 1 self.info.update_uploads_in_progress() diff --git a/onionshare_gui/receive_mode/info.py b/onionshare_gui/receive_mode/info.py index 0f5bc298..bc4aada8 100644 --- a/onionshare_gui/receive_mode/info.py +++ b/onionshare_gui/receive_mode/info.py @@ -46,17 +46,39 @@ class ReceiveModeInfo(QtWidgets.QWidget): self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) self.toggle_button.clicked.connect(self.toggle_uploads) + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel() + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + # Layout layout = QtWidgets.QHBoxLayout() layout.addStretch() layout.addWidget(self.in_progress_uploads_count) layout.addWidget(self.completed_uploads_count) + layout.addWidget(self.indicator_label) layout.addWidget(self.toggle_button) self.setLayout(layout) self.update_uploads_completed() self.update_uploads_in_progress() + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Uploads is hidden. + """ + if increment and not self.receive_mode.uploads.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + self.indicator_label.show() + def update_uploads_completed(self): """ Update the 'Uploads completed' info widget. @@ -94,6 +116,10 @@ class ReceiveModeInfo(QtWidgets.QWidget): self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) self.toggle_button.setFlat(False) + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + self.receive_mode.resize_window() def show_less(self): diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 48574cc7..8cda2e61 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -206,7 +206,73 @@ class Upload(QtWidgets.QWidget): self.label.setText(text) -class Uploads(QtWidgets.QScrollArea): +class UploadList(QtWidgets.QListWidget): + """ + List of upload progess bars. + """ + def __init__(self, common): + super(UploadList, self).__init__() + self.common = common + + self.uploads = {} + + self.setMinimumHeight(205) + self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + + def add(self, upload_id, content_length): + """ + Add a new upload progress bar. + """ + upload = Upload(self.common, upload_id, content_length) + self.uploads[upload_id] = upload + + item = QtWidgets.QListWidgetItem() + self.addItem(item) + self.setItemWidget(item, 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 + """ + # Remove all items from list + while True: + item = self.takeItem(0) + if not item: + break + + # Close all progress bars + for upload in self.uploads.values(): + upload.progress_bar.close() + self.uploads = {} + + +class Uploads(QtWidgets.QWidget): """ The uploads chunk of the GUI. This lists all of the active upload progress bars, as well as information about each upload. @@ -216,105 +282,100 @@ class Uploads(QtWidgets.QScrollArea): self.common = common self.common.log('Uploads', '__init__') - self.resizeEvent = None - - self.uploads = {} - - self.setWindowTitle(strings._('gui_uploads', True)) - 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) + # When there are no uploads + empty_image = QtWidgets.QLabel() + empty_image.setAlignment(QtCore.Qt.AlignCenter) + empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png')))) + empty_text = QtWidgets.QLabel(strings._('gui_no_uploads', True)) + empty_text.setAlignment(QtCore.Qt.AlignCenter) + empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(empty_image) + empty_layout.addWidget(empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setLayout(empty_layout) + + # When there are uploads + self.upload_list = UploadList(self.common) + + # Upload header uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - self.clear_history_button.clicked.connect(self.reset) - self.clear_history_button.hide() - - - self.uploads_layout = QtWidgets.QVBoxLayout() + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + upload_header = QtWidgets.QHBoxLayout() + upload_header.addWidget(uploads_label) + upload_header.addStretch() + upload_header.addWidget(clear_button) + + # Upload layout + not_empty_layout = QtWidgets.QVBoxLayout() + not_empty_layout.addLayout(upload_header) + not_empty_layout.addWidget(self.upload_list) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(not_empty_layout) - widget = QtWidgets.QWidget() + # Layout 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) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) + self.setLayout(layout) - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.vbar.setValue(maximum) + # Reset once at the beginning + self.reset() 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() + + # Hide empty, show not empty + self.empty.hide() + self.not_empty.show() # Add it to the list - upload = Upload(self.common, upload_id, content_length) - self.uploads[upload_id] = upload - self.uploads_layout.addWidget(upload) + self.upload_list.add(upload_id, content_length) def update(self, upload_id, progress): """ Update the progress of an upload. """ - self.uploads[upload_id].update(progress) + self.upload_list.update(upload_id, 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) + self.upload_list.rename(upload_id, old_filename, new_filename) def finished(self, upload_id): """ An upload has finished. """ - self.uploads[upload_id].finished() + self.upload_list.finished(upload_id) 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() + self.upload_list.cancel(upload_id) 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.upload_list.reset() - 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 + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 73e8faf5..bb2376ef 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -194,6 +194,8 @@ class Downloads(QtWidgets.QWidget): """ Add a new download progress bar. """ + self.common.log('Downloads', 'add', 'download_id: {}, content_length: {}'.format(download_id, content_length)) + # Hide empty, show not empty self.empty.hide() self.not_empty.show() diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py index a191933d..f8a68df7 100644 --- a/onionshare_gui/share_mode/info.py +++ b/onionshare_gui/share_mode/info.py @@ -57,7 +57,7 @@ class ShareModeInfo(QtWidgets.QWidget): self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) self.update_indicator() - # Info layout + # Layout layout = QtWidgets.QHBoxLayout() layout.addWidget(self.label) layout.addStretch() diff --git a/share/locale/en.json b/share/locale/en.json index 5821eea2..c7beb6ba 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -175,7 +175,7 @@ "systray_download_page_loaded_message": "A user loaded the download page", "systray_upload_page_loaded_message": "A user loaded the upload page", "gui_uploads": "Upload History", - "gui_no_uploads": "No uploads yet.", + "gui_no_uploads": "No Uploads Yet", "gui_clear_history": "Clear All", "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", -- cgit v1.2.3-54-g00ecf From 4aed7c0f9d1f272b9bd408cea6752cc4a115dc5b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 12:00:22 -0700 Subject: Use correct variable name for Downloads --- onionshare_gui/share_mode/downloads.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index bb2376ef..e567443a 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -92,11 +92,11 @@ class DownloadList(QtWidgets.QListWidget): self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - def add(self, download_id, total_bytes): + def add(self, download_id, content_length): """ Add a new download progress bar. """ - download = Download(self.common, download_id, total_bytes) + download = Download(self.common, download_id, content_length) self.downloads[download_id] = download item = QtWidgets.QListWidgetItem() @@ -190,7 +190,7 @@ class Downloads(QtWidgets.QWidget): # Reset once at the beginning self.reset() - def add(self, download_id, total_bytes): + def add(self, download_id, content_length): """ Add a new download progress bar. """ @@ -201,7 +201,7 @@ class Downloads(QtWidgets.QWidget): self.not_empty.show() # Add it to the list - self.download_list.add(download_id, total_bytes) + self.download_list.add(download_id, content_length) def update(self, download_id, downloaded_bytes): """ -- cgit v1.2.3-54-g00ecf From ffad77930f881de67381cbf9de12fadacdcc17df Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 12:19:01 -0700 Subject: Switch Downloads from QListWidget to QScrollArea --- onionshare_gui/share_mode/downloads.py | 44 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index e567443a..8eade23c 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -23,8 +23,9 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -class Download(object): +class Download(QtWidgets.QWidget): def __init__(self, common, download_id, total_bytes): + super(Download, self).__init__() self.common = common self.download_id = download_id @@ -32,6 +33,8 @@ class Download(object): self.total_bytes = total_bytes self.downloaded_bytes = 0 + self.setStyleSheet('QWidget { border: 1px solid red; }') + # Progress bar self.progress_bar = QtWidgets.QProgressBar() self.progress_bar.setTextVisible(True) @@ -43,6 +46,11 @@ class Download(object): self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) self.progress_bar.total_bytes = total_bytes + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.progress_bar) + self.setLayout(layout) + # Start at 0 self.update(0) @@ -78,9 +86,9 @@ class Download(object): self.started) -class DownloadList(QtWidgets.QListWidget): +class DownloadList(QtWidgets.QScrollArea): """ - List of download progess bars. + List of download progress bars. """ def __init__(self, common): super(DownloadList, self).__init__() @@ -88,9 +96,20 @@ class DownloadList(QtWidgets.QListWidget): self.downloads = {} - self.setMinimumHeight(205) - self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + self.downloads_layout = QtWidgets.QVBoxLayout() + self.downloads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + widget = QtWidgets.QWidget() + widget.setLayout(self.downloads_layout) + self.setWidget(widget) + + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) def add(self, download_id, content_length): """ @@ -98,10 +117,7 @@ class DownloadList(QtWidgets.QListWidget): """ download = Download(self.common, download_id, content_length) self.downloads[download_id] = download - - item = QtWidgets.QListWidgetItem() - self.addItem(item) - self.setItemWidget(item, download.progress_bar) + self.downloads_layout.addWidget(download) def update(self, download_id, downloaded_bytes): """ @@ -119,14 +135,8 @@ class DownloadList(QtWidgets.QListWidget): """ Reset the downloads back to zero """ - # Remove all items from list - while True: - item = self.takeItem(0) - if not item: - break - - # Close all progress bars for download in self.downloads.values(): + self.downloads_layout.removeWidget(download.progress_bar) download.progress_bar.close() self.downloads = {} -- cgit v1.2.3-54-g00ecf From d87115d21e8fc11979e416b9308019c9a7866bfe Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 13:41:12 -0700 Subject: Fix Downloads scroll area so internal widget is always the right size --- onionshare_gui/share_mode/downloads.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 8eade23c..50e7f0ef 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -96,12 +96,24 @@ class DownloadList(QtWidgets.QScrollArea): self.downloads = {} + # The layout that holds all of the downloads self.downloads_layout = QtWidgets.QVBoxLayout() + self.downloads_layout.setContentsMargins(0, 0, 0, 0) self.downloads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.downloads_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area widget = QtWidgets.QWidget() - widget.setLayout(self.downloads_layout) + widget.setLayout(wrapper_layout) self.setWidget(widget) + self.setWidgetResizable(True) + # Other scroll area settings self.setBackgroundRole(QtGui.QPalette.Light) self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) -- cgit v1.2.3-54-g00ecf From fa4ebbf263f02a07315972b805fd111e19463550 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 13:47:00 -0700 Subject: Convert Uploads to a QScrollArea also --- onionshare_gui/receive_mode/uploads.py | 44 ++++++++++++++++++++++------------ onionshare_gui/share_mode/downloads.py | 2 +- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 8cda2e61..f08b35cc 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -206,7 +206,7 @@ class Upload(QtWidgets.QWidget): self.label.setText(text) -class UploadList(QtWidgets.QListWidget): +class UploadList(QtWidgets.QScrollArea): """ List of upload progess bars. """ @@ -216,9 +216,32 @@ class UploadList(QtWidgets.QListWidget): self.uploads = {} - self.setMinimumHeight(205) - self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + # The layout that holds all of the uploads + self.uploads_layout = QtWidgets.QVBoxLayout() + self.uploads_layout.setContentsMargins(0, 0, 0, 0) + self.uploads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.uploads_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area + widget = QtWidgets.QWidget() + widget.setLayout(wrapper_layout) + self.setWidget(widget) + self.setWidgetResizable(True) + + # Other scroll area settings + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) def add(self, upload_id, content_length): """ @@ -226,10 +249,7 @@ class UploadList(QtWidgets.QListWidget): """ upload = Upload(self.common, upload_id, content_length) self.uploads[upload_id] = upload - - item = QtWidgets.QListWidgetItem() - self.addItem(item) - self.setItemWidget(item, upload) + self.uploads_layout.addWidget(upload) def update(self, upload_id, progress): """ @@ -260,14 +280,8 @@ class UploadList(QtWidgets.QListWidget): """ Reset the uploads back to zero """ - # Remove all items from list - while True: - item = self.takeItem(0) - if not item: - break - - # Close all progress bars for upload in self.uploads.values(): + self.uploads_layout.removeWidget(upload) upload.progress_bar.close() self.uploads = {} diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index 50e7f0ef..e78231ad 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -148,7 +148,7 @@ class DownloadList(QtWidgets.QScrollArea): Reset the downloads back to zero """ for download in self.downloads.values(): - self.downloads_layout.removeWidget(download.progress_bar) + self.downloads_layout.removeWidget(download) download.progress_bar.close() self.downloads = {} -- cgit v1.2.3-54-g00ecf From 4b3a68bb655c20ab2ccd6bfe8f9c492bcbd02ea9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 14:40:55 -0700 Subject: Got the indicator label to display in the correct location for share mode --- onionshare/common.py | 5 +++-- onionshare_gui/share_mode/info.py | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 43a6ac43..fb3b1e7a 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -284,9 +284,10 @@ class Common(object): color: #ffffff; background-color: #f44449; font-weight: bold; - padding: 5px; - border-radius: 5px; font-size: 10px; + padding: 2px; + border-radius: 8px; + text-align: center; }""", 'downloads_uploads_progress_bar': """ diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py index f8a68df7..b69820d6 100644 --- a/onionshare_gui/share_mode/info.py +++ b/onionshare_gui/share_mode/info.py @@ -45,7 +45,7 @@ class ShareModeInfo(QtWidgets.QWidget): # Toggle button self.toggle_button = QtWidgets.QPushButton() self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(30) + self.toggle_button.setFixedWidth(35) self.toggle_button.setFixedHeight(30) self.toggle_button.setFlat(True) self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) @@ -53,17 +53,24 @@ class ShareModeInfo(QtWidgets.QWidget): # Keep track of indicator self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel() + self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) self.update_indicator() + """ + # Add it to the toggle button + toggle_button_layout = QtWidgets.QHBoxLayout() + toggle_button_layout.addSpacing(10) + toggle_button_layout.addWidget(self.indicator_label) + self.toggle_button.setLayout(toggle_button_layout) + """ + # Layout layout = QtWidgets.QHBoxLayout() layout.addWidget(self.label) layout.addStretch() layout.addWidget(self.in_progress_downloads_count) layout.addWidget(self.completed_downloads_count) - layout.addWidget(self.indicator_label) layout.addWidget(self.toggle_button) self.setLayout(layout) @@ -90,6 +97,8 @@ class ShareModeInfo(QtWidgets.QWidget): if self.indicator_count == 0: self.indicator_label.hide() else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) self.indicator_label.show() def update_downloads_completed(self): -- cgit v1.2.3-54-g00ecf From b4de634b7ac3a12450bc99a5e962dab16f486778 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 14:43:13 -0700 Subject: Fix indicator label display for receive mode --- onionshare_gui/receive_mode/info.py | 7 ++++--- onionshare_gui/share_mode/info.py | 8 -------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/onionshare_gui/receive_mode/info.py b/onionshare_gui/receive_mode/info.py index bc4aada8..c23f8496 100644 --- a/onionshare_gui/receive_mode/info.py +++ b/onionshare_gui/receive_mode/info.py @@ -40,7 +40,7 @@ class ReceiveModeInfo(QtWidgets.QWidget): # Toggle button self.toggle_button = QtWidgets.QPushButton() self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(30) + self.toggle_button.setFixedWidth(35) self.toggle_button.setFixedHeight(30) self.toggle_button.setFlat(True) self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) @@ -48,7 +48,7 @@ class ReceiveModeInfo(QtWidgets.QWidget): # Keep track of indicator self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel() + self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) self.update_indicator() @@ -57,7 +57,6 @@ class ReceiveModeInfo(QtWidgets.QWidget): layout.addStretch() layout.addWidget(self.in_progress_uploads_count) layout.addWidget(self.completed_uploads_count) - layout.addWidget(self.indicator_label) layout.addWidget(self.toggle_button) self.setLayout(layout) @@ -77,6 +76,8 @@ class ReceiveModeInfo(QtWidgets.QWidget): if self.indicator_count == 0: self.indicator_label.hide() else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) self.indicator_label.show() def update_uploads_completed(self): diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py index b69820d6..c692649c 100644 --- a/onionshare_gui/share_mode/info.py +++ b/onionshare_gui/share_mode/info.py @@ -57,14 +57,6 @@ class ShareModeInfo(QtWidgets.QWidget): self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) self.update_indicator() - """ - # Add it to the toggle button - toggle_button_layout = QtWidgets.QHBoxLayout() - toggle_button_layout.addSpacing(10) - toggle_button_layout.addWidget(self.indicator_label) - self.toggle_button.setLayout(toggle_button_layout) - """ - # Layout layout = QtWidgets.QHBoxLayout() layout.addWidget(self.label) -- cgit v1.2.3-54-g00ecf From 8ca34fadd9895b7e2b16c13e5d315cc40e396c97 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 14:49:06 -0700 Subject: Fix crash when clicking Help from the systray --- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/settings_dialog.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 51190ea3..dd15fe12 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -65,7 +65,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.settings_action = menu.addAction(strings._('gui_settings_window_title', True)) self.settings_action.triggered.connect(self.open_settings) help_action = menu.addAction(strings._('gui_settings_button_help', True)) - help_action.triggered.connect(SettingsDialog.help_clicked) + help_action.triggered.connect(SettingsDialog.open_help) exit_action = menu.addAction(strings._('systray_menu_exit', True)) exit_action.triggered.connect(self.close) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 3cd25d31..39f08128 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -883,8 +883,12 @@ class SettingsDialog(QtWidgets.QDialog): Help button clicked. """ self.common.log('SettingsDialog', 'help_clicked') - help_site = 'https://github.com/micahflee/onionshare/wiki' - QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_site)) + SettingsDialog.open_help() + + @staticmethod + def open_help(): + help_url = 'https://github.com/micahflee/onionshare/wiki' + QtGui.QDesktopServices.openUrl(QtCore.QUrl(help_url)) def settings_from_fields(self): """ -- cgit v1.2.3-54-g00ecf From 39dd0862d4c02391fa32bf5fd97b0ec721425abb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 15:12:05 -0700 Subject: Increase minimum window with to 460, and store it in a variable to stop repeating myself --- onionshare_gui/mode.py | 4 ++-- onionshare_gui/onionshare_gui.py | 4 +++- onionshare_gui/receive_mode/__init__.py | 2 +- onionshare_gui/share_mode/__init__.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py index 0fba029b..1a961149 100644 --- a/onionshare_gui/mode.py +++ b/onionshare_gui/mode.py @@ -50,7 +50,7 @@ class Mode(QtWidgets.QWidget): self.filenames = filenames - self.setMinimumWidth(450) + self.setMinimumWidth(self.common.min_window_width) # The web object gets created in init() self.web = None @@ -83,7 +83,7 @@ class Mode(QtWidgets.QWidget): # Hack to allow a minimum width on the main layout # Note: It's up to the downstream Mode to add this to its layout self.min_width_widget = QtWidgets.QWidget() - self.min_width_widget.setMinimumWidth(450) + self.min_width_widget.setMinimumWidth(self.common.min_window_width) def init(self): """ diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index dd15fe12..ced53ede 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -45,6 +45,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') + self.common.min_window_width = 460 self.onion = onion self.qtapp = qtapp @@ -169,7 +170,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.show() # Adjust window size, to start with a minimum window width - self.adjust_size(450) + self.adjust_size(self.common.min_window_width) # The server isn't active yet self.set_server_active(False) @@ -474,6 +475,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Adjust sizes of each mode for mode in [self.share_mode, self.receive_mode]: + self.qtapp.processEvents() adjust_size_widget(mode) # Adjust window size diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 2f61b2ca..6430382b 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -194,7 +194,7 @@ class ReceiveMode(Mode): self.resize_window() def resize_window(self): - min_width = 450 + min_width = self.common.min_window_width if self.uploads.isVisible(): min_width += 300 self.adjust_size.emit(min_width) diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 58801c45..c44e8beb 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -317,7 +317,7 @@ class ShareMode(Mode): self.downloads.reset() def resize_window(self): - min_width = 450 + min_width = self.common.min_window_width if self.downloads.isVisible(): min_width += 300 self.adjust_size.emit(min_width) -- cgit v1.2.3-54-g00ecf From 4710eaee4c35b46e4117021bf197a2f9aa07b666 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 18:24:11 -0700 Subject: Fix local GUI tests so they pass --- tests_gui_local/commontests.py | 47 +++++++++++------- .../onionshare_receive_mode_upload_test.py | 12 +++-- ...onshare_receive_mode_upload_test_public_mode.py | 22 ++++---- .../onionshare_share_mode_download_test.py | 52 +++++++++++-------- ...onshare_share_mode_download_test_public_mode.py | 52 +++++++++++-------- ...nionshare_share_mode_download_test_stay_open.py | 58 ++++++++++++---------- tests_gui_local/onionshare_slug_persistent_test.py | 42 +++++++++------- tests_gui_local/onionshare_timer_test.py | 8 +-- 8 files changed, 171 insertions(+), 122 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index de1ad9ab..870c2dbe 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -24,19 +24,13 @@ class CommonTests(object): '''Test that the status bar is visible''' self.assertTrue(self.gui.status_bar.isVisible()) - def test_info_widget_is_not_visible(self, mode): - '''Test that the info widget along top of screen is not shown''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + def test_info_widget_shows_less(self, mode): + '''Test that minimum information (no label) is displayed in the info bar''' if mode == 'share': - self.assertFalse(self.gui.share_mode.info_widget.isVisible()) - - def test_info_widget_is_visible(self, mode): - '''Test that the info widget along top of screen is shown''' + self.assertFalse(self.gui.share_mode.info.label.text() == "") if mode == 'receive': - self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + # There's no minimal display in receive mode + self.assertTrue(False) def test_click_mode(self, mode): '''Test that we can switch Mode by clicking the button''' @@ -47,14 +41,30 @@ class CommonTests(object): QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + def test_click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + if mode == 'receive': + currently_visible = self.gui.receive_mode.uploads.isVisible() + QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.receive_mode.uploads.isVisible(), not currently_visible) + if mode == 'share': + currently_visible = self.gui.receive_mode.uploads.isVisible() + QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.downloads.isVisible(), not currently_visible) + + def test_history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + if mode == 'receive': + self.assertFalse(self.gui.receive_mode.uploads.isVisible()) + if mode == 'share': + self.assertFalse(self.gui.share_mode.downloads.isVisible()) + def test_history_is_visible(self, mode): - '''Test that the History section is visible and that the relevant widget is present''' + '''Test that the History section is visible''' if mode == 'receive': self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) if mode == 'share': self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) def test_server_working_on_start_button_pressed(self, mode): '''Test we can start the service''' @@ -161,11 +171,11 @@ class CommonTests(object): def test_history_widgets_present(self, mode): '''Test that the relevant widgets are present in the history view after activity has taken place''' if mode == 'receive': - self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + self.assertFalse(self.gui.receive_mode.uploads.empty.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.not_empty.isVisible()) if mode == 'share': - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + self.assertFalse(self.gui.share_mode.downloads.empty.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.not_empty.isVisible()) def test_counter_incremented(self, mode, count): '''Test that the counter has incremented''' @@ -304,4 +314,3 @@ class CommonTests(object): def test_add_button_visible(self): '''Test that the add button should be visible''' self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 2aa2ed94..b53d5c06 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -94,15 +94,19 @@ class OnionShareGuiTest(unittest.TestCase): def test_server_status_bar_is_visible(self): CommonTests.test_server_status_bar_is_visible(self) - @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - @pytest.mark.run(order=6) def test_click_mode(self): CommonTests.test_click_mode(self, 'receive') + @pytest.mark.run(order=6) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'receive') + @pytest.mark.run(order=7) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'receive') + + @pytest.mark.run(order=8) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'receive') diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 30a290e7..5e5a6b77 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -95,34 +95,38 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - - @pytest.mark.run(order=6) def test_click_mode(self): CommonTests.test_click_mode(self, 'receive') + @pytest.mark.run(order=6) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'receive') + @pytest.mark.run(order=7) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'receive') + + @pytest.mark.run(order=8) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'receive') - @pytest.mark.run(order=8) + @pytest.mark.run(order=9) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - @pytest.mark.run(order=9) + @pytest.mark.run(order=10) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'receive') - @pytest.mark.run(order=10) + @pytest.mark.run(order=11) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=12) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'receive') - @pytest.mark.run(order=12) + @pytest.mark.run(order=13) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index c546fb61..40df6d98 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -97,90 +97,98 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_web_page(self): CommonTests.test_web_page(self, 'share', 'Total size', False) - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_download_share(self): CommonTests.test_download_share(self, False) - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'share') - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=25) + @pytest.mark.run(order=27) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=26) + @pytest.mark.run(order=28) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - @pytest.mark.run(order=27) + @pytest.mark.run(order=29) def test_add_button_visible(self): CommonTests.test_add_button_visible(self) diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index 764b5885..73d4c999 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -97,90 +97,98 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', True) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_web_page(self): CommonTests.test_web_page(self, 'share', 'Total size', True) - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_download_share(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'share') - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=25) + @pytest.mark.run(order=27) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=26) + @pytest.mark.run(order=28) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - @pytest.mark.run(order=27) + @pytest.mark.run(order=29) def test_add_button_visible(self): CommonTests.test_add_button_visible(self) diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index b92ff097..e849d224 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -97,102 +97,110 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', True) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_web_page(self): CommonTests.test_web_page(self, 'share', 'Total size', True) - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_download_share(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'share') - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_counter_incremented(self): CommonTests.test_counter_incremented(self, 'share', 1) - @pytest.mark.run(order=25) + @pytest.mark.run(order=27) def test_download_share_again(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=26) + @pytest.mark.run(order=28) def test_counter_incremented_again(self): CommonTests.test_counter_incremented(self, 'share', 2) - @pytest.mark.run(order=27) + @pytest.mark.run(order=29) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', True) - @pytest.mark.run(order=28) + @pytest.mark.run(order=30) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=29) + @pytest.mark.run(order=31) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - @pytest.mark.run(order=30) + @pytest.mark.run(order=32) def test_add_button_visible(self): CommonTests.test_add_button_visible(self) diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index 1e5614dc..5b53f7e0 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -94,68 +94,76 @@ class OnionShareGuiTest(unittest.TestCase): def test_server_status_bar_is_visible(self): CommonTests.test_server_status_bar_is_visible(self) - @pytest.mark.run(order=5) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - @pytest.mark.run(order=6) + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=7) + @pytest.mark.run(order=10) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=11) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=9) + @pytest.mark.run(order=12) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=13) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=11) + @pytest.mark.run(order=14) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=12) + @pytest.mark.run(order=15) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) global slug slug = self.gui.share_mode.server_status.web.slug - @pytest.mark.run(order=13) + @pytest.mark.run(order=16) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=14) + @pytest.mark.run(order=17) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', True) - @pytest.mark.run(order=15) + @pytest.mark.run(order=18) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=16) + @pytest.mark.run(order=19) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - @pytest.mark.run(order=17) + @pytest.mark.run(order=20) def test_server_started_again(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') CommonTests.test_server_status_indicator_says_starting(self, 'share') CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=18) + @pytest.mark.run(order=21) def test_have_same_slug(self): '''Test that we have the same slug''' self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - @pytest.mark.run(order=19) + @pytest.mark.run(order=22) def test_server_is_stopped_again(self): CommonTests.test_server_is_stopped(self, 'share', True) CommonTests.test_web_service_is_stopped(self) diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py index 1a5134e2..701d9a21 100644 --- a/tests_gui_local/onionshare_timer_test.py +++ b/tests_gui_local/onionshare_timer_test.py @@ -97,12 +97,12 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') @pytest.mark.run(order=8) def test_set_timeout(self): -- cgit v1.2.3-54-g00ecf From d8c225a9c627ffd45c56d96569faaa4f863acb37 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 29 Sep 2018 18:58:27 -0700 Subject: Begin refactoring Tor tests to inherit from local tests --- tests_gui_local/__init__.py | 8 ++ tests_gui_tor/commontests.py | 311 +------------------------------------------ 2 files changed, 10 insertions(+), 309 deletions(-) diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py index e69de29b..76e5b1b9 100644 --- a/tests_gui_local/__init__.py +++ b/tests_gui_local/__init__.py @@ -0,0 +1,8 @@ +from .onionshare_receive_mode_upload_test_public_mode import OnionShareGuiTest as ReceiveMoveUploadTestPublicMode +from .onionshare_receive_mode_upload_test import OnionShareGuiTest as ReceiveModeUploadTest +from .onionshare_share_mode_download_test_public_mode import OnionShareGuiTest as ShareModeDownloadTestPublicMode +from .onionshare_share_mode_download_test import OnionShareGuiTest as ShareModeDownloadTest +from .onionshare_share_mode_download_test_stay_open import OnionShareGuiTest as ShareModeDownloadTestStayOpen +from .onionshare_slug_persistent_test import OnionShareGuiTest as SlugPersistentTest +from .onionshare_timer_test import OnionShareGuiTest as TimerTest +from .commontests import CommonTests diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py index a0d9bf5f..ea37279f 100644 --- a/tests_gui_tor/commontests.py +++ b/tests_gui_tor/commontests.py @@ -7,207 +7,13 @@ import zipfile from PyQt5 import QtCore, QtTest from onionshare import strings -class CommonTests(object): - def test_gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - def test_info_widget_is_not_visible(self, mode): - '''Test that the info widget along top of screen is not shown''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.info_widget.isVisible()) - - def test_info_widget_is_visible(self, mode): - '''Test that the info widget along top of screen is shown''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.info_widget.isVisible()) - - def test_click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if mode == 'share': - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - def test_history_is_visible(self, mode): - '''Test that the History section is visible and that the relevant widget is present''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.downloads.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - - def test_server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.receive_mode.server_status.status, 1) - if mode == 'share': - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.status, 1) - - def test_server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) - if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) - - def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - def test_a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(45000) - # Should now be in SERVER_STARTED state - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 2) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 2) - - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if mode == 'receive': - if not public_mode: - self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - if mode == 'share': - if not public_mode: - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') +from tests_gui_local import CommonTests as LocalCommonTests +class CommonTests(LocalCommonTests): def test_have_an_onion_service(self): '''Test that we have a valid Onion URL''' self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - def test_url_description_shown(self, mode): - '''Test that the URL label is showing''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) - - def test_have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) - - def test_server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) - if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) - - def test_web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) - - s = socks.socksocket() - s.settimeout(60) - s.connect((self.gui.app.onion_host, 80)) - - if not public_mode: - if mode == 'receive': - path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) - if mode == 'share': - path = '/{}'.format(self.gui.share_mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def test_history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) - - def test_counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.uploads_completed, count) - if mode == 'share': - self.assertEquals(self.gui.share_mode.downloads_completed, count) - - def test_server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - if stay_open: - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.status, 0) - - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) - if mode == 'share': - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) - def test_cancel_the_share(self, mode): '''Test that we can cancel this share before it's started up ''' if mode == 'share': @@ -222,119 +28,6 @@ class CommonTests(object): QtTest.QTest.mouseRelease(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(self.gui.receive_mode.server_status.status, 0) - - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - if mode == 'receive': - self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) - if mode == 'share': - self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - session = requests.session() - session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) - else: - path = 'http://{}/upload'.format(self.gui.app.onion_host) - response = session.post(path, files=files) - QtTest.QTest.qWait(4000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) - - s = socks.socksocket() - s.settimeout(60) - s.connect((self.gui.app.onion_host, 80)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(4000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - # Stealth tests def test_copy_have_hidserv_auth_button(self, mode): '''Test that the Copy HidservAuth button is shown''' -- cgit v1.2.3-54-g00ecf From 4ffc0ddb82a4232d2bbb3da7dbc5b12632cd2798 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 30 Sep 2018 16:52:48 +1000 Subject: Ignore attribute error when optimistically trying to cancel compression (we may have no ZipWriter object yet) --- onionshare_gui/share_mode/threads.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py index d6022746..24e2c242 100644 --- a/onionshare_gui/share_mode/threads.py +++ b/onionshare_gui/share_mode/threads.py @@ -56,5 +56,8 @@ class CompressThread(QtCore.QThread): # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True - if self.mode.web.zip_writer: + try: self.mode.web.zip_writer.cancel_compression = True + except AttributeError: + # we never made it as far as creating a ZipWriter object + pass -- cgit v1.2.3-54-g00ecf From 10ca75fc91d24810a3d0685feacec63320080586 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 30 Sep 2018 17:16:37 +1000 Subject: Add a test for #790 --- .../onionshare_790_cancel_on_second_share_test.py | 197 +++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 tests_gui_tor/onionshare_790_cancel_on_second_share_test.py diff --git a/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py b/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py new file mode 100644 index 00000000..731de4fd --- /dev/null +++ b/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .commontests import CommonTests + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + 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, False, 0) + + web = Web(common, False, True) + + 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, + "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' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + CommonTests.test_gui_loaded(self) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + CommonTests.test_windowTitle_seen(self) + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + CommonTests.test_settings_button_is_visible(self) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + CommonTests.test_server_status_bar_is_visible(self) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + CommonTests.test_file_selection_widget_has_a_file(self) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + CommonTests.test_info_widget_is_visible(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + CommonTests.test_deleting_only_file_hides_delete_button(self) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + CommonTests.test_file_selection_widget_readd_files(self) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=13) + def test_add_delete_buttons_hidden(self): + CommonTests.test_add_delete_buttons_hidden(self) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + CommonTests.test_settings_button_is_hidden(self) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + CommonTests.test_a_server_is_started(self, 'share') + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + CommonTests.test_a_web_server_is_running(self) + + @pytest.mark.run(order=17) + def test_have_a_slug(self): + CommonTests.test_have_a_slug(self, 'share', False) + + @pytest.mark.run(order=18) + def test_have_an_onion(self): + CommonTests.test_have_an_onion_service(self) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + CommonTests.test_url_description_shown(self, 'share') + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + CommonTests.test_have_copy_url_button(self, 'share') + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_started(self): + CommonTests.test_server_status_indicator_says_started(self, 'share') + + @pytest.mark.run(order=22) + def test_server_is_stopped(self): + CommonTests.test_server_is_stopped(self, 'share', True) + + @pytest.mark.run(order=23) + def test_web_service_is_stopped(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=24) + def test_server_working_on_start_button_pressed_round2(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + + @pytest.mark.run(order=25) + def test_server_status_indicator_says_starting_round2(self): + CommonTests.test_server_status_indicator_says_starting(self, 'share') + + @pytest.mark.run(order=26) + def test_cancel_the_share(self): + CommonTests.test_cancel_the_share(self, 'share') + + @pytest.mark.run(order=27) + def test_server_is_stopped_round2(self): + CommonTests.test_server_is_stopped(self, 'share', False) + + @pytest.mark.run(order=28) + def test_web_service_is_stopped_round2(self): + CommonTests.test_web_service_is_stopped(self) + + @pytest.mark.run(order=29) + def test_add_button_visible(self): + CommonTests.test_add_button_visible(self) + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From 84de1b51a0d777fb5fe3aa28bc05c1e79a92d4d8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 30 Sep 2018 17:43:45 +1000 Subject: Replace deprecated assertEquals with assertEqual in tests --- tests_gui_local/commontests.py | 26 +++++++++++++------------- tests_gui_tor/commontests.py | 26 +++++++++++++------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index de1ad9ab..e67a91f2 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -69,9 +69,9 @@ class CommonTests(object): def test_server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) def test_settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' @@ -123,9 +123,9 @@ class CommonTests(object): def test_server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) def test_web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' @@ -170,19 +170,19 @@ class CommonTests(object): def test_counter_incremented(self, mode, count): '''Test that the counter has incremented''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.uploads_completed, count) + self.assertEqual(self.gui.receive_mode.uploads_completed, count) if mode == 'share': - self.assertEquals(self.gui.share_mode.downloads_completed, count) + self.assertEqual(self.gui.share_mode.downloads_completed, count) def test_server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' if mode == 'receive': QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) + self.assertEqual(self.gui.receive_mode.server_status.status, 0) if mode == 'share': if stay_open: QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.status, 0) + self.assertEqual(self.gui.share_mode.server_status.status, 0) def test_web_service_is_stopped(self): '''Test that the web server also stopped''' @@ -195,12 +195,12 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) if mode == 'share': if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) # Auto-stop timer tests def test_set_timeout(self, mode, timeout): @@ -260,7 +260,7 @@ class CommonTests(object): '''Test that we can also delete a file by clicking on its [X] widget''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) def test_file_selection_widget_readd_files(self): '''Re-add some files to the list so we can share''' @@ -299,7 +299,7 @@ class CommonTests(object): zip = zipfile.ZipFile('/tmp/download.zip') QtTest.QTest.qWait(2000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) def test_add_button_visible(self): '''Test that the add button should be visible''' diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py index a0d9bf5f..f58f0504 100644 --- a/tests_gui_tor/commontests.py +++ b/tests_gui_tor/commontests.py @@ -69,9 +69,9 @@ class CommonTests(object): def test_server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) def test_settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' @@ -126,9 +126,9 @@ class CommonTests(object): def test_server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) def test_web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' @@ -176,19 +176,19 @@ class CommonTests(object): def test_counter_incremented(self, mode, count): '''Test that the counter has incremented''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.uploads_completed, count) + self.assertEqual(self.gui.receive_mode.uploads_completed, count) if mode == 'share': - self.assertEquals(self.gui.share_mode.downloads_completed, count) + self.assertEqual(self.gui.share_mode.downloads_completed, count) def test_server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' if mode == 'receive': QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) + self.assertEqual(self.gui.receive_mode.server_status.status, 0) if mode == 'share': if stay_open: QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.status, 0) + self.assertEqual(self.gui.share_mode.server_status.status, 0) def test_web_service_is_stopped(self): '''Test that the web server also stopped''' @@ -201,12 +201,12 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) if mode == 'share': if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) def test_cancel_the_share(self, mode): '''Test that we can cancel this share before it's started up ''' @@ -286,7 +286,7 @@ class CommonTests(object): '''Test that we can also delete a file by clicking on its [X] widget''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) def test_file_selection_widget_readd_files(self): '''Re-add some files to the list so we can share''' @@ -328,7 +328,7 @@ class CommonTests(object): zip = zipfile.ZipFile('/tmp/download.zip') QtTest.QTest.qWait(4000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) def test_add_button_visible(self): '''Test that the add button should be visible''' -- cgit v1.2.3-54-g00ecf From f5c7acf8f260679a9fec4b9772e8d8bb8d8d41e4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 10:57:13 -0700 Subject: Fix Tor tests so they pass, too --- tests_gui_local/__init__.py | 7 --- tests_gui_tor/commontests.py | 9 ++++ .../onionshare_receive_mode_upload_test.py | 36 +++++++------ ...onshare_receive_mode_upload_test_public_mode.py | 46 +++++++++-------- .../onionshare_share_mode_cancel_share_test.py | 8 --- .../onionshare_share_mode_download_test.py | 16 +++--- ...onshare_share_mode_download_test_public_mode.py | 54 ++++++++++--------- ...nionshare_share_mode_download_test_stay_open.py | 60 ++++++++++++---------- .../onionshare_share_mode_persistent_test.py | 46 ++++++++++------- .../onionshare_share_mode_stealth_test.py | 44 +++++++++------- ...nshare_share_mode_tor_connection_killed_test.py | 46 ++++++++++------- tests_gui_tor/onionshare_timer_test.py | 28 ++++++---- .../onionshare_tor_connection_killed_test.py | 46 ++++++++++------- 13 files changed, 252 insertions(+), 194 deletions(-) diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py index 76e5b1b9..bb2b2182 100644 --- a/tests_gui_local/__init__.py +++ b/tests_gui_local/__init__.py @@ -1,8 +1 @@ -from .onionshare_receive_mode_upload_test_public_mode import OnionShareGuiTest as ReceiveMoveUploadTestPublicMode -from .onionshare_receive_mode_upload_test import OnionShareGuiTest as ReceiveModeUploadTest -from .onionshare_share_mode_download_test_public_mode import OnionShareGuiTest as ShareModeDownloadTestPublicMode -from .onionshare_share_mode_download_test import OnionShareGuiTest as ShareModeDownloadTest -from .onionshare_share_mode_download_test_stay_open import OnionShareGuiTest as ShareModeDownloadTestStayOpen -from .onionshare_slug_persistent_test import OnionShareGuiTest as SlugPersistentTest -from .onionshare_timer_test import OnionShareGuiTest as TimerTest from .commontests import CommonTests diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py index ea37279f..a1e420fd 100644 --- a/tests_gui_tor/commontests.py +++ b/tests_gui_tor/commontests.py @@ -10,6 +10,15 @@ from onionshare import strings from tests_gui_local import CommonTests as LocalCommonTests class CommonTests(LocalCommonTests): + def test_a_server_is_started(self, mode): + '''Test that the server has started (overriding from local tests to wait for longer)''' + QtTest.QTest.qWait(45000) + # Should now be in SERVER_STARTED state + if mode == 'receive': + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + if mode == 'share': + self.assertEqual(self.gui.share_mode.server_status.status, 2) + def test_have_an_onion_service(self): '''Test that we have a valid Onion URL''' self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test.py b/tests_gui_tor/onionshare_receive_mode_upload_test.py index 5be400e2..7c340037 100644 --- a/tests_gui_tor/onionshare_receive_mode_upload_test.py +++ b/tests_gui_tor/onionshare_receive_mode_upload_test.py @@ -94,15 +94,19 @@ class OnionShareGuiTest(unittest.TestCase): def test_server_status_bar_is_visible(self): CommonTests.test_server_status_bar_is_visible(self) - @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - @pytest.mark.run(order=6) def test_click_mode(self): CommonTests.test_click_mode(self, 'receive') + @pytest.mark.run(order=6) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'receive') + @pytest.mark.run(order=7) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'receive') + + @pytest.mark.run(order=8) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'receive') @@ -134,51 +138,51 @@ class OnionShareGuiTest(unittest.TestCase): def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=16) + @pytest.mark.run(order=20) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'receive') - @pytest.mark.run(order=17) + @pytest.mark.run(order=21) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'receive') - @pytest.mark.run(order=18) + @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'receive') - @pytest.mark.run(order=19) + @pytest.mark.run(order=23) def test_web_page(self): CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) - @pytest.mark.run(order=20) + @pytest.mark.run(order=24) def test_upload_file(self): CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') - @pytest.mark.run(order=21) + @pytest.mark.run(order=25) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'receive') - @pytest.mark.run(order=22) + @pytest.mark.run(order=26) def test_counter_incremented(self): CommonTests.test_counter_incremented(self, 'receive', 1) - @pytest.mark.run(order=23) + @pytest.mark.run(order=27) def test_upload_same_file_is_renamed(self): CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') - @pytest.mark.run(order=24) + @pytest.mark.run(order=28) def test_upload_count_incremented_again(self): CommonTests.test_counter_incremented(self, 'receive', 2) - @pytest.mark.run(order=25) + @pytest.mark.run(order=29) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'receive', False) - @pytest.mark.run(order=26) + @pytest.mark.run(order=30) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=27) + @pytest.mark.run(order=31) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py index 9c9553a4..65bf5c89 100644 --- a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py @@ -95,34 +95,38 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_server_status_bar_is_visible(self) @pytest.mark.run(order=5) - def test_info_widget_is_not_visible(self): - CommonTests.test_info_widget_is_not_visible(self, 'receive') - - @pytest.mark.run(order=6) def test_click_mode(self): CommonTests.test_click_mode(self, 'receive') + @pytest.mark.run(order=6) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'receive') + @pytest.mark.run(order=7) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'receive') + + @pytest.mark.run(order=8) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'receive') - @pytest.mark.run(order=8) + @pytest.mark.run(order=9) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - @pytest.mark.run(order=9) + @pytest.mark.run(order=10) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'receive') - @pytest.mark.run(order=10) + @pytest.mark.run(order=11) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=12) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'receive') - @pytest.mark.run(order=12) + @pytest.mark.run(order=13) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) @@ -134,51 +138,51 @@ class OnionShareGuiTest(unittest.TestCase): def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=16) + @pytest.mark.run(order=20) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'receive') - @pytest.mark.run(order=17) + @pytest.mark.run(order=21) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'receive') - @pytest.mark.run(order=18) + @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'receive') - @pytest.mark.run(order=19) + @pytest.mark.run(order=23) def test_web_page(self): CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) - @pytest.mark.run(order=20) + @pytest.mark.run(order=24) def test_upload_file(self): CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') - @pytest.mark.run(order=21) + @pytest.mark.run(order=25) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'receive') - @pytest.mark.run(order=22) + @pytest.mark.run(order=26) def test_counter_incremented(self): CommonTests.test_counter_incremented(self, 'receive', 1) - @pytest.mark.run(order=23) + @pytest.mark.run(order=27) def test_upload_same_file_is_renamed(self): CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') - @pytest.mark.run(order=24) + @pytest.mark.run(order=28) def test_upload_count_incremented_again(self): CommonTests.test_counter_incremented(self, 'receive', 2) - @pytest.mark.run(order=25) + @pytest.mark.run(order=29) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'receive', False) - @pytest.mark.run(order=26) + @pytest.mark.run(order=30) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=27) + @pytest.mark.run(order=31) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) diff --git a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py index 466109d7..cdab8f85 100644 --- a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py +++ b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py @@ -96,14 +96,6 @@ class OnionShareGuiTest(unittest.TestCase): def test_file_selection_widget_has_a_file(self): CommonTests.test_file_selection_widget_has_a_file(self) - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) diff --git a/tests_gui_tor/onionshare_share_mode_download_test.py b/tests_gui_tor/onionshare_share_mode_download_test.py index 1c8e1b6c..2bf26690 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test.py +++ b/tests_gui_tor/onionshare_share_mode_download_test.py @@ -97,20 +97,20 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) + def test_history_is_visible(self): + CommonTests.test_history_is_visible(self, 'share') @pytest.mark.run(order=10) def test_file_selection_widget_readd_files(self): diff --git a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py index c292e729..4792994d 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py @@ -97,94 +97,102 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', True) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_web_page(self): CommonTests.test_web_page(self, 'share', 'Total size', True) - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_download_share(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'share') - @pytest.mark.run(order=25) + @pytest.mark.run(order=27) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=26) + @pytest.mark.run(order=28) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=27) + @pytest.mark.run(order=29) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - @pytest.mark.run(order=28) + @pytest.mark.run(order=30) def test_add_button_visible(self): CommonTests.test_add_button_visible(self) diff --git a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py index 7838316f..92d52169 100644 --- a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py @@ -97,106 +97,114 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', True) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_web_page(self): CommonTests.test_web_page(self, 'share', 'Total size', True) - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_download_share(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_history_widgets_present(self): CommonTests.test_history_widgets_present(self, 'share') - @pytest.mark.run(order=25) + @pytest.mark.run(order=27) def test_counter_incremented(self): CommonTests.test_counter_incremented(self, 'share', 1) - @pytest.mark.run(order=26) + @pytest.mark.run(order=28) def test_download_share_again(self): CommonTests.test_download_share(self, True) - @pytest.mark.run(order=27) + @pytest.mark.run(order=29) def test_counter_incremented_again(self): CommonTests.test_counter_incremented(self, 'share', 2) - @pytest.mark.run(order=28) + @pytest.mark.run(order=30) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', True) - @pytest.mark.run(order=29) + @pytest.mark.run(order=31) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=30) + @pytest.mark.run(order=32) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - @pytest.mark.run(order=31) + @pytest.mark.run(order=33) def test_add_button_visible(self): CommonTests.test_add_button_visible(self) diff --git a/tests_gui_tor/onionshare_share_mode_persistent_test.py b/tests_gui_tor/onionshare_share_mode_persistent_test.py index 3cffaab6..6b9fbe16 100644 --- a/tests_gui_tor/onionshare_share_mode_persistent_test.py +++ b/tests_gui_tor/onionshare_share_mode_persistent_test.py @@ -95,79 +95,87 @@ class OnionShareGuiTest(unittest.TestCase): def test_server_status_bar_is_visible(self): CommonTests.test_server_status_bar_is_visible(self) - @pytest.mark.run(order=5) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - @pytest.mark.run(order=6) + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') + + @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=7) + @pytest.mark.run(order=10) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=11) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=9) + @pytest.mark.run(order=12) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=13) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=11) + @pytest.mark.run(order=14) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=12) + @pytest.mark.run(order=15) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) global slug slug = self.gui.share_mode.server_status.web.slug - @pytest.mark.run(order=13) + @pytest.mark.run(order=16) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) global onion_host onion_host = self.gui.app.onion_host - @pytest.mark.run(order=14) + @pytest.mark.run(order=17) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=15) + @pytest.mark.run(order=18) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', True) - @pytest.mark.run(order=16) + @pytest.mark.run(order=19) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=20) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - @pytest.mark.run(order=18) + @pytest.mark.run(order=21) def test_server_started_again(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') CommonTests.test_server_status_indicator_says_starting(self, 'share') CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=19) + @pytest.mark.run(order=22) def test_have_same_slug(self): '''Test that we have the same slug''' self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - @pytest.mark.run(order=20) + @pytest.mark.run(order=23) def test_have_same_onion(self): '''Test that we have the same onion''' self.assertEqual(self.gui.app.onion_host, onion_host) - @pytest.mark.run(order=21) + @pytest.mark.run(order=24) def test_server_is_stopped_again(self): CommonTests.test_server_is_stopped(self, 'share', True) CommonTests.test_web_service_is_stopped(self) diff --git a/tests_gui_tor/onionshare_share_mode_stealth_test.py b/tests_gui_tor/onionshare_share_mode_stealth_test.py index aaf6fbc6..876efde2 100644 --- a/tests_gui_tor/onionshare_share_mode_stealth_test.py +++ b/tests_gui_tor/onionshare_share_mode_stealth_test.py @@ -97,74 +97,82 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_copy_have_hidserv_auth_button(self): CommonTests.test_copy_have_hidserv_auth_button(self, 'share') - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_hidserv_auth_string(self): CommonTests.test_hidserv_auth_string(self) diff --git a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py index 861b7ccc..37abc825 100644 --- a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py @@ -97,78 +97,86 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_tor_killed_statusbar_message_shown(self): CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) diff --git a/tests_gui_tor/onionshare_timer_test.py b/tests_gui_tor/onionshare_timer_test.py index b76106d9..2b64b998 100644 --- a/tests_gui_tor/onionshare_timer_test.py +++ b/tests_gui_tor/onionshare_timer_test.py @@ -97,42 +97,50 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_set_timeout(self): CommonTests.test_set_timeout(self, 'share', 120) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_timeout_widget_hidden(self): CommonTests.test_timeout_widget_hidden(self, 'share') - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_timeout(self): CommonTests.test_server_timed_out(self, 'share', 125000) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) diff --git a/tests_gui_tor/onionshare_tor_connection_killed_test.py b/tests_gui_tor/onionshare_tor_connection_killed_test.py index 861b7ccc..37abc825 100644 --- a/tests_gui_tor/onionshare_tor_connection_killed_test.py +++ b/tests_gui_tor/onionshare_tor_connection_killed_test.py @@ -97,78 +97,86 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_file_selection_widget_has_a_file(self) @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') + def test_info_widget_shows_less(self): + CommonTests.test_info_widget_shows_less(self, 'share') @pytest.mark.run(order=7) + def test_history_is_not_visible(self): + CommonTests.test_history_is_not_visible(self, 'share') + + @pytest.mark.run(order=8) + def test_click_toggle_history(self): + CommonTests.test_click_toggle_history(self, 'share') + + @pytest.mark.run(order=9) def test_history_is_visible(self): CommonTests.test_history_is_visible(self, 'share') - @pytest.mark.run(order=8) + @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): CommonTests.test_deleting_only_file_hides_delete_button(self) - @pytest.mark.run(order=9) + @pytest.mark.run(order=11) def test_add_a_file_and_delete_using_its_delete_widget(self): CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - @pytest.mark.run(order=10) + @pytest.mark.run(order=12) def test_file_selection_widget_readd_files(self): CommonTests.test_file_selection_widget_readd_files(self) - @pytest.mark.run(order=11) + @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): CommonTests.test_server_working_on_start_button_pressed(self, 'share') - @pytest.mark.run(order=12) + @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): CommonTests.test_server_status_indicator_says_starting(self, 'share') - @pytest.mark.run(order=13) + @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): CommonTests.test_add_delete_buttons_hidden(self) - @pytest.mark.run(order=14) + @pytest.mark.run(order=16) def test_settings_button_is_hidden(self): CommonTests.test_settings_button_is_hidden(self) - @pytest.mark.run(order=15) + @pytest.mark.run(order=17) def test_a_server_is_started(self): CommonTests.test_a_server_is_started(self, 'share') - @pytest.mark.run(order=16) + @pytest.mark.run(order=18) def test_a_web_server_is_running(self): CommonTests.test_a_web_server_is_running(self) - @pytest.mark.run(order=17) + @pytest.mark.run(order=19) def test_have_a_slug(self): CommonTests.test_have_a_slug(self, 'share', False) - @pytest.mark.run(order=18) + @pytest.mark.run(order=20) def test_have_an_onion(self): CommonTests.test_have_an_onion_service(self) - @pytest.mark.run(order=19) + @pytest.mark.run(order=21) def test_url_description_shown(self): CommonTests.test_url_description_shown(self, 'share') - @pytest.mark.run(order=20) + @pytest.mark.run(order=22) def test_have_copy_url_button(self): CommonTests.test_have_copy_url_button(self, 'share') - @pytest.mark.run(order=21) + @pytest.mark.run(order=23) def test_server_status_indicator_says_started(self): CommonTests.test_server_status_indicator_says_started(self, 'share') - @pytest.mark.run(order=22) + @pytest.mark.run(order=24) def test_tor_killed_statusbar_message_shown(self): CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') - @pytest.mark.run(order=23) + @pytest.mark.run(order=25) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'share', False) - @pytest.mark.run(order=24) + @pytest.mark.run(order=26) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) -- cgit v1.2.3-54-g00ecf From 3fd75819953e18a652f0746b68e7ec99eeb9c4ba Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 11:41:07 -0700 Subject: Test the history indicator widget, in local GUI tests --- tests_gui_local/commontests.py | 53 ++++++++++++++++++++++ .../onionshare_receive_mode_upload_test.py | 8 +++- ...onshare_receive_mode_upload_test_public_mode.py | 8 +++- .../onionshare_share_mode_download_test.py | 6 +++ ...onshare_share_mode_download_test_public_mode.py | 6 +++ ...nionshare_share_mode_download_test_stay_open.py | 6 +++ tests_gui_local/onionshare_slug_persistent_test.py | 6 +++ 7 files changed, 89 insertions(+), 4 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index 870c2dbe..21e8cfad 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -52,6 +52,59 @@ class CommonTests(object): QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) self.assertEqual(self.gui.share_mode.downloads.isVisible(), not currently_visible) + def test_history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + if mode == 'receive': + # Make sure history is toggled off + if self.gui.receive_mode.uploads.isVisible(): + QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.receive_mode.uploads.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(self.gui.receive_mode.info.indicator_label.isVisible()) + + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(self.gui.receive_mode.info.indicator_label.isVisible()) + self.assertEqual(self.gui.receive_mode.info.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.receive_mode.info.indicator_label.isVisible()) + + if mode == 'share': + # Make sure history is toggled off + if self.gui.share_mode.downloads.isVisible(): + QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.downloads.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(self.gui.share_mode.info.indicator_label.isVisible()) + + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(self.gui.share_mode.info.indicator_label.isVisible()) + self.assertEqual(self.gui.share_mode.info.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.info.indicator_label.isVisible()) + def test_history_is_not_visible(self, mode): '''Test that the History section is not visible''' if mode == 'receive': diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index b53d5c06..19674aa3 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -171,14 +171,18 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_counter_incremented(self, 'receive', 2) @pytest.mark.run(order=24) + def test_history_indicator(self): + CommonTests.test_history_indicator(self, 'receive', False) + + @pytest.mark.run(order=25) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'receive', False) - @pytest.mark.run(order=25) + @pytest.mark.run(order=26) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=26) + @pytest.mark.run(order=27) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 5e5a6b77..e3f85731 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -171,14 +171,18 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_counter_incremented(self, 'receive', 2) @pytest.mark.run(order=24) + def test_history_indicator(self): + CommonTests.test_history_indicator(self, 'receive', True) + + @pytest.mark.run(order=25) def test_server_is_stopped(self): CommonTests.test_server_is_stopped(self, 'receive', False) - @pytest.mark.run(order=25) + @pytest.mark.run(order=26) def test_web_service_is_stopped(self): CommonTests.test_web_service_is_stopped(self) - @pytest.mark.run(order=26) + @pytest.mark.run(order=27) def test_server_status_indicator_says_closed(self): CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 40df6d98..c4a60101 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -192,6 +192,12 @@ class OnionShareGuiTest(unittest.TestCase): def test_add_button_visible(self): CommonTests.test_add_button_visible(self) + @pytest.mark.run(order=30) + def test_history_indicator(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_history_indicator(self, 'share', False) + if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index 73d4c999..a10ee4c2 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -192,6 +192,12 @@ class OnionShareGuiTest(unittest.TestCase): def test_add_button_visible(self): CommonTests.test_add_button_visible(self) + @pytest.mark.run(order=30) + def test_history_indicator(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_history_indicator(self, 'share', True) + if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index e849d224..8426c264 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -204,6 +204,12 @@ class OnionShareGuiTest(unittest.TestCase): def test_add_button_visible(self): CommonTests.test_add_button_visible(self) + @pytest.mark.run(order=33) + def test_history_indicator(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_history_indicator(self, 'share', True) + if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index 5b53f7e0..9fb623dd 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -168,6 +168,12 @@ class OnionShareGuiTest(unittest.TestCase): CommonTests.test_server_is_stopped(self, 'share', True) CommonTests.test_web_service_is_stopped(self) + @pytest.mark.run(order=23) + def test_history_indicator(self): + CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_history_indicator(self, 'share', False) + if __name__ == "__main__": unittest.main() -- cgit v1.2.3-54-g00ecf From d28f38b1a03a98de2a79ce750672255cacde36fa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 14:45:21 -0700 Subject: Add locale to Settings, and make it default to the system locale, or English --- onionshare/settings.py | 17 ++++++++++++++++- tests/test_onionshare_settings.py | 7 ++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index adcfc7a3..d231a976 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -21,6 +21,7 @@ along with this program. If not, see . import json import os import platform +import locale from . import strings @@ -47,6 +48,12 @@ class Settings(object): else: self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') + # Available languages in this version of OnionShare + self.available_locales = [ + 'cs', 'da', 'de', 'en', 'eo', 'es', 'fi', + 'fr', 'it', 'nl', 'no', 'pt', 'ru', 'tr' + ] + # These are the default settings. They will get overwritten when loading from disk self.default_settings = { 'version': self.common.version, @@ -74,7 +81,8 @@ class Settings(object): 'slug': '', 'hidservauth_string': '', '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.fill_in_defaults() @@ -88,6 +96,13 @@ class Settings(object): if key not in self._settings: 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): """ Returns the path of the settings file. diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 1f1ef528..371b2d27 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -40,7 +40,7 @@ def settings_obj(sys_onionshare_dev_mode, platform_linux): class TestSettings: def test_init(self, settings_obj): - assert settings_obj._settings == settings_obj.default_settings == { + expected_settings = { 'version': 'DUMMY_VERSION_1.2.3', 'connection_type': 'bundled', 'control_port_address': '127.0.0.1', @@ -68,6 +68,11 @@ class TestSettings: 'receive_allow_receiver_shutdown': True, '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): del settings_obj._settings['version'] -- cgit v1.2.3-54-g00ecf From 64a44284654badbba5296451cf91bff8e050ad94 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 15:01:02 -0700 Subject: Make Settings.available_locales be a dictionary mapping locale codes to language names in that language --- onionshare/settings.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index d231a976..d3788739 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -48,11 +48,25 @@ class Settings(object): else: self.common.log('Settings', '__init__', 'Supplied config does not exist or is unreadable. Falling back to default location') - # Available languages in this version of OnionShare - self.available_locales = [ - 'cs', 'da', 'de', 'en', 'eo', 'es', 'fi', - 'fr', 'it', 'nl', 'no', 'pt', 'ru', 'tr' - ] + # Dictionary of available languages in this version of OnionShare, + # mapped to the language name, in that language. + # TODO: Update language names to not be in English + self.available_locales = { + 'cs': 'Croatian', + 'da': 'Danish', + 'de': 'German', + 'en': 'English', + 'eo': 'Esperanto', + 'es': 'Spanish', + 'fi': 'Finnish', + 'fr': 'French', + 'it': 'Italian', + 'nl': 'Dutch', + 'no': 'Norweigan', + 'pt': 'Portuguese', + 'ru': 'Russian', + 'tr': 'Turkish' + } # These are the default settings. They will get overwritten when loading from disk self.default_settings = { -- cgit v1.2.3-54-g00ecf From 9a19a7aecd41e266ea917cbd039dc11162841c24 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 16:14:14 -0700 Subject: Allow switching locales from the settings dialog --- onionshare_gui/settings_dialog.py | 27 +++++++++++++++++++++++++++ share/locale/en.json | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 3cd25d31..5966b954 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -227,6 +227,22 @@ class SettingsDialog(QtWidgets.QDialog): if self.system != 'Windows' and self.system != 'Darwin': autoupdate_group.hide() + # Language settings + + # Populate the dropdown with all of OnionShare's available languages + self.language_combobox = QtWidgets.QComboBox() + 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.QVBoxLayout() + language_layout.addWidget(self.language_combobox) + language_group = QtWidgets.QGroupBox(strings._("gui_settings_language_label", True)) + language_group.setLayout(language_layout) + # Connection type: either automatic, control port, or socket file # Bundled Tor @@ -431,6 +447,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(autoupdate_group) + left_col_layout.addWidget(language_group) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() @@ -524,6 +541,10 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_timestamp = self.old_settings.get('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') if connection_type == 'bundled': if self.connection_type_bundled_radio.isEnabled(): @@ -936,6 +957,12 @@ class SettingsDialog(QtWidgets.QDialog): if not self.stealth_checkbox.isChecked(): 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(): settings.set('connection_type', 'bundled') if self.connection_type_automatic_radio.isChecked(): diff --git a/share/locale/en.json b/share/locale/en.json index 0f0f0cf4..903f8945 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -180,5 +180,6 @@ "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", - "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": "Select language" } -- cgit v1.2.3-54-g00ecf From 9d5f9d18edcc082e24b9d59783b088d16e5dfc37 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 16:18:40 -0700 Subject: Translate language names to their own language --- onionshare/settings.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index d3788739..1fbda807 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -49,23 +49,22 @@ class Settings(object): 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. - # TODO: Update language names to not be in English + # mapped to the language name, in that language self.available_locales = { - 'cs': 'Croatian', - 'da': 'Danish', - 'de': 'German', - 'en': 'English', - 'eo': 'Esperanto', - 'es': 'Spanish', - 'fi': 'Finnish', - 'fr': 'French', - 'it': 'Italian', - 'nl': 'Dutch', - 'no': 'Norweigan', - 'pt': 'Portuguese', - 'ru': 'Russian', - 'tr': 'Turkish' + '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 -- cgit v1.2.3-54-g00ecf From 23c55bc95b2c99a6efad03f0c6c0fda91c18cf26 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 16:23:46 -0700 Subject: Change how language selection setting looks --- onionshare_gui/settings_dialog.py | 13 ++++++------- share/locale/en.json | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 5966b954..709ab059 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -228,20 +228,19 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group.hide() # Language settings - - # Populate the dropdown with all of OnionShare's available languages + language_label = QtWidgets.QLabel(strings._("gui_settings_language_label", True)) 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.QVBoxLayout() + language_layout = QtWidgets.QHBoxLayout() + language_layout.addWidget(language_label) language_layout.addWidget(self.language_combobox) - language_group = QtWidgets.QGroupBox(strings._("gui_settings_language_label", True)) - language_group.setLayout(language_layout) + language_layout.addStretch() # Connection type: either automatic, control port, or socket file @@ -447,7 +446,7 @@ class SettingsDialog(QtWidgets.QDialog): left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(autoupdate_group) - left_col_layout.addWidget(language_group) + left_col_layout.addLayout(language_layout) left_col_layout.addStretch() right_col_layout = QtWidgets.QVBoxLayout() diff --git a/share/locale/en.json b/share/locale/en.json index 903f8945..05830e62 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -181,5 +181,5 @@ "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}", - "gui_settings_language_label": "Select language" + "gui_settings_language_label": "Preferred language" } -- cgit v1.2.3-54-g00ecf From c4f776c42ada8b6a8c2c8326dc91d3aa87670675 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 17:06:29 -0700 Subject: Set OnionShare language based on the locale stored in settings, and prompt user to restart OnionShare after changing their language --- onionshare/__init__.py | 9 +++++++-- onionshare/strings.py | 41 ++++++++++++++++++--------------------- onionshare_gui/__init__.py | 4 ++++ onionshare_gui/onionshare_gui.py | 5 +++-- onionshare_gui/settings_dialog.py | 30 +++++++++++++++++++--------- share/locale/en.json | 3 ++- 6 files changed, 56 insertions(+), 36 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 715c5571..2f0ef14c 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -33,7 +33,11 @@ def main(cwd=None): """ common = Common() + # Load settings right away, in order to get locale setting for strings + common.load_settings(config) strings.load_strings(common) + + # Display OnionShare banner print(strings._('version_string').format(common.version)) # OnionShare CLI in OSX needs to change current working directory (#132) @@ -88,8 +92,9 @@ def main(cwd=None): if not valid: sys.exit() - # Load settings - common.load_settings(config) + # Re-load settings, if a custom config was passed in + if config: + common.load_settings(config) # Debug mode? common.debug = debug diff --git a/onionshare/strings.py b/onionshare/strings.py index 3e9df56d..edae7849 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -22,36 +22,33 @@ import locale import os strings = {} +translations = {} -def load_strings(common, default="en"): +def load_strings(common): """ Loads translated strings and fallback to English if the translation does not exist. """ - global strings + global strings, translations - # find locale dir - locale_dir = common.get_resource_path('locale') - - # load all translations + # Load all translations translations = {} - for filename in os.listdir(locale_dir): - abs_filename = os.path.join(locale_dir, filename) - lang, ext = os.path.splitext(filename) - if ext == '.json': - with open(abs_filename, encoding='utf-8') as f: - translations[lang] = json.load(f) - - strings = translations[default] - lc, enc = locale.getdefaultlocale() - if lc: - lang = lc[:2] - if lang in translations: - # if a string doesn't exist, fallback to English - for key in translations[default]: - if key in translations[lang]: - strings[key] = translations[lang][key] + for locale in common.settings.available_locales: + locale_dir = common.get_resource_path('locale') + filename = os.path.join(locale_dir, "{}.json".format(locale)) + with open(filename, encoding='utf-8') as f: + translations[locale] = json.load(f) + + # Build strings + default_locale = 'en' + current_locale = common.settings.get('locale') + strings = {} + for s in translations[default_locale]: + if s in translations[current_locale]: + strings[s] = translations[current_locale][s] + else: + strings[s] = translations[default_locale][s] def translated(k, gui=False): diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 99db635a..51565cb6 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -59,7 +59,11 @@ def main(): common = Common() common.define_css() + # Load settings right away, in order to get locale setting for strings + common.load_settings() strings.load_strings(common) + + # Display OnionShare banner print(strings._('version_string').format(common.version)) # Allow Ctrl-C to smoothly quit the program instead of throwing an exception diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 83f3a7e0..b3d6afef 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -57,9 +57,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png'))) self.setMinimumWidth(850) - # Load settings + # Load settings, if a custom config was passed in self.config = config - self.common.load_settings(self.config) + if self.config: + self.common.load_settings(self.config) # System tray menu = QtWidgets.QMenu() diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 709ab059..7aff85a5 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -830,8 +830,29 @@ class SettingsDialog(QtWidgets.QDialog): """ self.common.log('SettingsDialog', 'save_clicked') + def changed(s1, s2, keys): + """ + Compare the Settings objects s1 and s2 and return true if any values + have changed for the given keys. + """ + for key in keys: + if s1.get(key) != s2.get(key): + return True + 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 @@ -840,15 +861,6 @@ class SettingsDialog(QtWidgets.QDialog): if not self.local_only: if self.onion.is_authenticated(): self.common.log('SettingsDialog', 'save_clicked', 'Connected to Tor') - def changed(s1, s2, keys): - """ - Compare the Settings objects s1 and s2 and return true if any values - have changed for the given keys. - """ - for key in keys: - if s1.get(key) != s2.get(key): - return True - return False if changed(settings, self.old_settings, [ 'connection_type', 'control_port_address', diff --git a/share/locale/en.json b/share/locale/en.json index 05830e62..02577f0d 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -181,5 +181,6 @@ "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", "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_label": "Preferred language", + "gui_settings_language_changed_notice": "Restart OnionShare for your change in language to take effect." } -- cgit v1.2.3-54-g00ecf From 60afaaf338d2c49ffd93a605a90bb645cf37955a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 17:18:56 -0700 Subject: Make printing the settings filename as debug statement instead of print --- onionshare/settings.py | 2 +- share/locale/en.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 1fbda807..811ad032 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -164,7 +164,7 @@ class Settings(object): except: pass 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): return self._settings[key] diff --git a/share/locale/en.json b/share/locale/en.json index 02577f0d..f5d887b5 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -106,7 +106,6 @@ "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout_checkbox": "Use auto-stop timer", "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_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 {}:{}.", -- cgit v1.2.3-54-g00ecf From c572ab996a54d41c26d3e5ce99d5b38ebc406323 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 17:35:58 -0700 Subject: Fix tests --- onionshare/settings.py | 9 +++++++-- tests/conftest.py | 7 +++++-- tests/test_onionshare_strings.py | 12 ++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 811ad032..ed827cbd 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -122,8 +122,13 @@ class Settings(object): """ p = platform.system() if p == 'Windows': - appdata = os.environ['APPDATA'] - return '{}\\OnionShare\\onionshare.json'.format(appdata) + try: + appdata = os.environ['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': return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json') else: diff --git a/tests/conftest.py b/tests/conftest.py index 8ac7efb8..3ae6fd52 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import tempfile import pytest -from onionshare import common, web, settings +from onionshare import common, web, settings, strings @pytest.fixture def temp_dir_1024(): @@ -151,7 +151,10 @@ def time_strftime(monkeypatch): @pytest.fixture def common_obj(): - return common.Common() + _common = common.Common() + _common.settings = settings.Settings(_common) + strings.load_strings(_common) + return _common @pytest.fixture def settings_obj(sys_onionshare_dev_mode, platform_linux): diff --git a/tests/test_onionshare_strings.py b/tests/test_onionshare_strings.py index d3d40c8f..6d39598c 100644 --- a/tests/test_onionshare_strings.py +++ b/tests/test_onionshare_strings.py @@ -32,12 +32,6 @@ from onionshare import strings # return 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(): assert callable(strings._) and isinstance(strings._, types.FunctionType) @@ -53,11 +47,13 @@ class TestLoadStrings: def test_load_strings_loads_other_languages( self, common_obj, locale_fr, sys_onionshare_dev_mode): """ 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." def test_load_invalid_locale( self, common_obj, locale_invalid, sys_onionshare_dev_mode): """ load_strings() raises a KeyError for an invalid locale """ with pytest.raises(KeyError): - strings.load_strings(common_obj, 'XX') + common_obj.settings.set('locale', 'XX') + strings.load_strings(common_obj) -- cgit v1.2.3-54-g00ecf From a1f5b5964a71b2c815347bc5c88c2849e8aac675 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 17:47:10 -0700 Subject: Remove the useless gui=True arg getting passed intro strings._ all over the place --- onionshare/onion.py | 2 +- onionshare/strings.py | 2 +- onionshare_gui/__init__.py | 4 +- onionshare_gui/onionshare_gui.py | 50 +++++------ onionshare_gui/receive_mode/__init__.py | 14 +-- onionshare_gui/receive_mode/uploads.py | 16 ++-- onionshare_gui/server_status.py | 30 +++---- onionshare_gui/settings_dialog.py | 134 ++++++++++++++-------------- onionshare_gui/share_mode/__init__.py | 26 +++--- onionshare_gui/share_mode/downloads.py | 8 +- onionshare_gui/share_mode/file_selection.py | 12 +-- onionshare_gui/tor_connection_dialog.py | 6 +- 12 files changed, 152 insertions(+), 152 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index c45ae72e..cb73e976 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -247,7 +247,7 @@ class Onion(object): self.c = Controller.from_socket_file(path=self.tor_control_socket) self.c.authenticate() 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: try: diff --git a/onionshare/strings.py b/onionshare/strings.py index edae7849..b730933d 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -51,7 +51,7 @@ def load_strings(common): strings[s] = translations[default_locale][s] -def translated(k, gui=False): +def translated(k): """ Returns a translated string. """ diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index 51565cb6..b5520071 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -100,10 +100,10 @@ def main(): valid = True for filename in filenames: 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 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 if not valid: sys.exit() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index b3d6afef..1b3a62a2 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -64,11 +64,11 @@ class OnionShareGui(QtWidgets.QMainWindow): # System tray 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) - help_action = menu.addAction(strings._('gui_settings_button_help', True)) + help_action = menu.addAction(strings._('gui_settings_button_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) self.system_tray = QtWidgets.QSystemTrayIcon(self) @@ -81,10 +81,10 @@ class OnionShareGui(QtWidgets.QMainWindow): self.system_tray.show() # 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.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.clicked.connect(self.receive_mode_clicked) self.settings_button = QtWidgets.QPushButton() @@ -224,24 +224,24 @@ class OnionShareGui(QtWidgets.QMainWindow): # Share mode 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_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: 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: 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: # Receive mode 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_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: 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: 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): # When the server stopped, cleanup the ephemeral onion service @@ -255,9 +255,9 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common.log('OnionShareGui', '_tor_connection_canceled') def ask(): - a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False) - settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True)) - quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True)) + 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')) + quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit')) a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole) a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole) a.setDefaultButton(settings_button) @@ -328,7 +328,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if self.common.platform == 'Windows' or self.common.platform == 'Darwin': if self.common.settings.get('use_autoupdate'): 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.update_available.connect(update_available) @@ -345,8 +345,8 @@ class OnionShareGui(QtWidgets.QMainWindow): # Have we lost connection to Tor somehow? if not self.onion.is_authenticated(): self.timer.stop() - self.status_bar.showMessage(strings._('gui_tor_connection_lost', True)) - self.system_tray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True)) + self.status_bar.showMessage(strings._('gui_tor_connection_lost')) + self.system_tray.showMessage(strings._('gui_tor_connection_lost'), strings._('gui_tor_connection_error_settings')) self.share_mode.handle_tor_broke() self.receive_mode.handle_tor_broke() @@ -400,7 +400,7 @@ class OnionShareGui(QtWidgets.QMainWindow): if event["type"] == Web.REQUEST_OTHER: 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() @@ -409,14 +409,14 @@ class OnionShareGui(QtWidgets.QMainWindow): When the URL gets copied to the clipboard, display this in the status bar. """ 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): """ When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. """ 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): """ @@ -454,14 +454,14 @@ class OnionShareGui(QtWidgets.QMainWindow): if server_status.status != server_status.STATUS_STOPPED: self.common.log('OnionShareGui', 'closeEvent, opening warning dialog') dialog = QtWidgets.QMessageBox() - dialog.setWindowTitle(strings._('gui_quit_title', True)) + dialog.setWindowTitle(strings._('gui_quit_title')) if self.mode == OnionShareGui.MODE_SHARE: - dialog.setText(strings._('gui_share_quit_warning', True)) + dialog.setText(strings._('gui_share_quit_warning')) else: - dialog.setText(strings._('gui_receive_quit_warning', True)) + dialog.setText(strings._('gui_receive_quit_warning')) dialog.setIcon(QtWidgets.QMessageBox.Critical) - quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole) - dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole) + quit_button = dialog.addButton(strings._('gui_quit_warning_quit'), QtWidgets.QMessageBox.YesRole) + dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit'), QtWidgets.QMessageBox.NoRole) dialog.setDefaultButton(dont_quit_button) reply = dialog.exec_() diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index 590dec65..1207a0f3 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -72,7 +72,7 @@ class ReceiveMode(Mode): self.info_widget.hide() # Receive mode info - self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) + self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) self.receive_info.setMinimumHeight(80) self.receive_info.setWordWrap(True) @@ -86,7 +86,7 @@ class ReceiveMode(Mode): """ 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): """ @@ -125,7 +125,7 @@ class ReceiveMode(Mode): """ 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): """ @@ -135,7 +135,7 @@ class ReceiveMode(Mode): self.uploads_in_progress += 1 self.update_uploads_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): """ @@ -148,7 +148,7 @@ class ReceiveMode(Mode): Handle REQUEST_CLOSE_SERVER event. """ 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): """ @@ -194,7 +194,7 @@ class ReceiveMode(Mode): else: image = self.common.get_resource_path('images/share_completed.png') self.info_completed_uploads_count.setText(' {1:d}'.format(image, self.uploads_completed)) - self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.uploads_completed)) + self.info_completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip').format(self.uploads_completed)) def update_uploads_in_progress(self): """ @@ -205,7 +205,7 @@ class ReceiveMode(Mode): else: image = self.common.get_resource_path('images/share_in_progress.png') self.info_in_progress_uploads_count.setText(' {1:d}'.format(image, self.uploads_in_progress)) - self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress)) + self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip').format(self.uploads_in_progress)) def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 48574cc7..33d993b3 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -113,7 +113,7 @@ class Upload(QtWidgets.QWidget): self.started = datetime.now() # 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 self.progress_bar = QtWidgets.QProgressBar() @@ -190,16 +190,16 @@ class Upload(QtWidgets.QWidget): 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', True).format( + text = strings._('gui_upload_finished').format( self.started.strftime("%b %d, %I:%M%p") ) 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.ended.strftime("%I:%M%p") ) 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.ended.strftime("%b %d, %I:%M%p") ) @@ -220,7 +220,7 @@ class Uploads(QtWidgets.QScrollArea): self.uploads = {} - self.setWindowTitle(strings._('gui_uploads', True)) + self.setWindowTitle(strings._('gui_uploads')) self.setWidgetResizable(True) self.setMinimumHeight(150) self.setMinimumWidth(350) @@ -229,10 +229,10 @@ class Uploads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() self.vbar.rangeChanged.connect(self.resizeScroll) - uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) + 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', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + 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() diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 32135ca4..f1ffe995 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -61,7 +61,7 @@ class ServerStatus(QtWidgets.QWidget): self.resizeEvent(None) # 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.setDisplayFormat("hh:mm A MMM d, yy") if self.local_only: @@ -101,11 +101,11 @@ class ServerStatus(QtWidgets.QWidget): self.url.setMinimumSize(self.url.sizeHint()) 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.setStyleSheet(self.common.css['server_status_url_buttons']) 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.setStyleSheet(self.common.css['server_status_url_buttons']) 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') 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: - 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 if self.common.settings.get('save_private_key'): if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent', True)) + self.url_description.setToolTip(strings._('gui_url_label_onetime_and_persistent')) else: - self.url_description.setToolTip(strings._('gui_url_label_persistent', True)) + self.url_description.setToolTip(strings._('gui_url_label_persistent')) else: if self.mode == ServerStatus.MODE_SHARE and self.common.settings.get('close_after_first_download'): - self.url_description.setToolTip(strings._('gui_url_label_onetime', True)) + self.url_description.setToolTip(strings._('gui_url_label_onetime')) 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.show() @@ -223,9 +223,9 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setStyleSheet(self.common.css['server_status_button_stopped']) self.server_button.setEnabled(True) 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: - self.server_button.setText(strings._('gui_receive_start_server', True)) + self.server_button.setText(strings._('gui_receive_start_server')) self.server_button.setToolTip('') if self.common.settings.get('shutdown_timeout'): self.shutdown_timeout_container.show() @@ -233,15 +233,15 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.setStyleSheet(self.common.css['server_status_button_started']) self.server_button.setEnabled(True) 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: - 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'): self.shutdown_timeout_container.hide() 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: - 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: self.server_button.setStyleSheet(self.common.css['server_status_button_working']) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7aff85a5..b9e0453a 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -47,7 +47,7 @@ class SettingsDialog(QtWidgets.QDialog): self.local_only = local_only 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.system = platform.system() @@ -57,8 +57,8 @@ class SettingsDialog(QtWidgets.QDialog): # Use a slug or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() self.public_mode_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox", True)) - public_mode_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Public-Mode")) + self.public_mode_checkbox.setText(strings._("gui_settings_public_mode_checkbox")) + 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) public_mode_label.setOpenExternalLinks(True) @@ -74,8 +74,8 @@ class SettingsDialog(QtWidgets.QDialog): # Whether or not to use a shutdown ('auto-stop') timer self.shutdown_timeout_checkbox = QtWidgets.QCheckBox() self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Checked) - self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox", True)) - shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this", True).format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) + self.shutdown_timeout_checkbox.setText(strings._("gui_settings_shutdown_timeout_checkbox")) + shutdown_timeout_label = QtWidgets.QLabel(strings._("gui_settings_whats_this").format("https://github.com/micahflee/onionshare/wiki/Using-the-Auto-Stop-Timer")) shutdown_timeout_label.setStyleSheet(self.common.css['settings_whats_this']) shutdown_timeout_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) shutdown_timeout_label.setOpenExternalLinks(True) @@ -91,9 +91,9 @@ class SettingsDialog(QtWidgets.QDialog): # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() 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) - 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) 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) self.save_private_key_checkbox = QtWidgets.QCheckBox() 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) - 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) save_private_key_label.setOpenExternalLinks(True) @@ -125,9 +125,9 @@ class SettingsDialog(QtWidgets.QDialog): # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() 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) - 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) use_stealth_label.setOpenExternalLinks(True) @@ -140,12 +140,12 @@ class SettingsDialog(QtWidgets.QDialog): self.use_stealth_widget = QtWidgets.QWidget() 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.setMinimumSize(hidservauth_details.sizeHint()) 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.hide() @@ -159,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog): general_group_layout.addWidget(hidservauth_details) 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) # Sharing options @@ -167,19 +167,19 @@ class SettingsDialog(QtWidgets.QDialog): # Close after first download self.close_after_first_download_checkbox = QtWidgets.QCheckBox() 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_group_layout = QtWidgets.QVBoxLayout() 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) # 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.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_layout = QtWidgets.QHBoxLayout() downloads_layout.addWidget(downloads_label) @@ -189,13 +189,13 @@ class SettingsDialog(QtWidgets.QDialog): # Allow the receiver to shutdown the server self.receive_allow_receiver_shutdown_checkbox = QtWidgets.QCheckBox() 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_group_layout = QtWidgets.QVBoxLayout() receiving_group_layout.addLayout(downloads_layout) 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) # Automatic updates options @@ -203,13 +203,13 @@ class SettingsDialog(QtWidgets.QDialog): # Autoupdate self.autoupdate_checkbox = QtWidgets.QCheckBox() 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 self.autoupdate_timestamp = QtWidgets.QLabel() # 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) # We can't check for updates if not connected to Tor if not self.onion.connected_to_tor: @@ -220,7 +220,7 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group_layout.addWidget(self.autoupdate_checkbox) autoupdate_group_layout.addWidget(self.autoupdate_timestamp) 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 is only available for Windows and Mac (Linux updates using package manager) @@ -228,7 +228,7 @@ class SettingsDialog(QtWidgets.QDialog): autoupdate_group.hide() # Language settings - language_label = QtWidgets.QLabel(strings._("gui_settings_language_label", True)) + 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()} @@ -245,7 +245,7 @@ class SettingsDialog(QtWidgets.QDialog): # Connection type: either automatic, control port, or socket file # 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) # Bundled Tor doesn't work on dev mode in Windows or Mac @@ -255,27 +255,27 @@ class SettingsDialog(QtWidgets.QDialog): # Bridge options for bundled tor # 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) # obfs4 option radio # 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() 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) 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) # meek_lite-azure option radio # 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() 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) 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) # meek_lite currently not supported on the version of obfs4proxy bundled with TorBrowser @@ -283,10 +283,10 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_meek_lite_azure_radio.hide() # 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_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.setOpenExternalLinks(True) self.tor_bridges_use_custom_textbox = QtWidgets.QPlainTextEdit() @@ -313,14 +313,14 @@ class SettingsDialog(QtWidgets.QDialog): self.bridges.setLayout(bridges_layout) # 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) # 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) - 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_port = QtWidgets.QLineEdit() connection_type_control_port_extras_layout = QtWidgets.QHBoxLayout() @@ -333,10 +333,10 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_control_port_extras.hide() # 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) - 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() connection_type_socket_file_extras_layout = QtWidgets.QHBoxLayout() connection_type_socket_file_extras_layout.addWidget(connection_type_socket_file_extras_label) @@ -347,7 +347,7 @@ class SettingsDialog(QtWidgets.QDialog): self.connection_type_socket_file_extras.hide() # 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_port = QtWidgets.QLineEdit() connection_type_socks_layout = QtWidgets.QHBoxLayout() @@ -362,14 +362,14 @@ class SettingsDialog(QtWidgets.QDialog): # Authentication options # 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) # 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) - 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('') authenticate_password_extras_layout = QtWidgets.QHBoxLayout() authenticate_password_extras_layout.addWidget(authenticate_password_extras_label) @@ -384,7 +384,7 @@ class SettingsDialog(QtWidgets.QDialog): authenticate_group_layout.addWidget(self.authenticate_no_auth_radio) authenticate_group_layout.addWidget(self.authenticate_password_radio) 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) # Put the radios into their own group so they are exclusive @@ -393,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_control_port_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) # 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.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.hide() # 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) connection_type_test_button_layout = QtWidgets.QHBoxLayout() connection_type_test_button_layout.addWidget(self.connection_type_test_button) @@ -420,13 +420,13 @@ class SettingsDialog(QtWidgets.QDialog): connection_type_layout.addLayout(connection_type_test_button_layout) # 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.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) version_label = QtWidgets.QLabel('OnionShare {0:s}'.format(self.common.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) buttons_layout = QtWidgets.QHBoxLayout() buttons_layout.addWidget(version_label) @@ -623,7 +623,7 @@ class SettingsDialog(QtWidgets.QDialog): self.tor_bridges_use_custom_textbox_options.hide() # 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'): - 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): """ @@ -736,7 +736,7 @@ class SettingsDialog(QtWidgets.QDialog): """ downloads_dir = self.downloads_dir_lineedit.text() selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_downloads_label', True), downloads_dir) + strings._('gui_settings_downloads_label'), downloads_dir) if selected_dir: self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir)) @@ -766,7 +766,7 @@ class SettingsDialog(QtWidgets.QDialog): 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 - 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 onion.cleanup() @@ -802,19 +802,19 @@ class SettingsDialog(QtWidgets.QDialog): # Check for updates 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() def update_not_available(): - Alert(self.common, strings._('update_not_available', True)) + Alert(self.common, strings._('update_not_available')) close_forced_update_thread() 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() 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() forced_update_thread = UpdateThread(self.common, self.onion, self.config, force=True) @@ -905,7 +905,7 @@ class SettingsDialog(QtWidgets.QDialog): """ self.common.log('SettingsDialog', 'cancel_clicked') 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() else: self.close() @@ -938,7 +938,7 @@ class SettingsDialog(QtWidgets.QDialog): if self.save_private_key_checkbox.isChecked(): # force the legacy mode on use_legacy_v2_onions = True - settings.set('save_private_key', True) + settings.set('save_private_key') settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) @@ -950,7 +950,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('hidservauth_string', '') if use_legacy_v2_onions: - settings.set('use_legacy_v2_onions', True) + settings.set('use_legacy_v2_onions') else: settings.set('use_legacy_v2_onions', False) # If we are not using legacy mode, but we previously had persistence turned on, force it off! @@ -984,7 +984,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('connection_type', 'socket_file') if self.autoupdate_checkbox.isChecked(): - settings.set('use_autoupdate', True) + settings.set('use_autoupdate') else: settings.set('use_autoupdate', False) @@ -1004,19 +1004,19 @@ class SettingsDialog(QtWidgets.QDialog): # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set('no_bridges', True) + settings.set('no_bridges') settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4', True) + settings.set('tor_bridges_use_obfs4') settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure', True) + settings.set('tor_bridges_use_meek_lite_azure') settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) @@ -1047,8 +1047,8 @@ class SettingsDialog(QtWidgets.QDialog): new_bridges = ''.join(new_bridges) settings.set('tor_bridges_use_custom_bridges', new_bridges) else: - Alert(self.common, strings._('gui_settings_tor_bridges_invalid', True)) - settings.set('no_bridges', True) + Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) + settings.set('no_bridges') return False return settings @@ -1071,11 +1071,11 @@ class SettingsDialog(QtWidgets.QDialog): dt = datetime.datetime.fromtimestamp(autoupdate_timestamp) last_checked = dt.strftime('%B %d, %Y %H:%M') else: - last_checked = strings._('gui_settings_autoupdate_timestamp_never', True) - self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp', True).format(last_checked)) + last_checked = strings._('gui_settings_autoupdate_timestamp_never') + self.autoupdate_timestamp.setText(strings._('gui_settings_autoupdate_timestamp').format(last_checked)) def _tor_status_update(self, progress, summary): - self.tor_status.setText('{}
    {}% {}'.format(strings._('connecting_to_tor', True), progress, summary)) + self.tor_status.setText('{}
    {}% {}'.format(strings._('connecting_to_tor'), progress, summary)) self.qtapp.processEvents() if 'Done' in summary: self.tor_status.hide() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py index 90fce49a..feb3b86b 100644 --- a/onionshare_gui/share_mode/__init__.py +++ b/onionshare_gui/share_mode/__init__.py @@ -118,7 +118,7 @@ class ShareMode(Mode): """ 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): """ @@ -127,11 +127,11 @@ class ShareMode(Mode): # If there were no attempts to download the share, or all downloads are done, we can stop if self.web.share_mode.download_count == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_timeout', True)) + self.server_status_label.setText(strings._('close_on_timeout')) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('timeout_download_still_running', True)) + self.server_status_label.setText(strings._('timeout_download_still_running')) return False def start_server_custom(self): @@ -178,7 +178,7 @@ class ShareMode(Mode): # Warn about sending large files over Tor 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() def start_server_error_custom(self): @@ -223,7 +223,7 @@ class ShareMode(Mode): """ 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): """ @@ -237,7 +237,7 @@ class ShareMode(Mode): self.downloads_in_progress += 1 self.update_downloads_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): """ @@ -247,7 +247,7 @@ class ShareMode(Mode): # Is the download complete? 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 the total 'completed downloads' info self.downloads_completed += 1 @@ -260,7 +260,7 @@ class ShareMode(Mode): if self.common.settings.get('close_after_first_download'): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically', True)) + self.server_status_label.setText(strings._('closing_automatically')) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel(event["data"]["id"]) @@ -276,7 +276,7 @@ class ShareMode(Mode): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 self.update_downloads_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) def on_reload_settings(self): """ @@ -302,9 +302,9 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) 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: - 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: self.primary_action.hide() @@ -332,7 +332,7 @@ class ShareMode(Mode): else: image = self.common.get_resource_path('images/share_completed.png') self.info_completed_downloads_count.setText(' {1:d}'.format(image, self.downloads_completed)) - self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.downloads_completed)) + self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip').format(self.downloads_completed)) def update_downloads_in_progress(self): """ @@ -343,7 +343,7 @@ class ShareMode(Mode): else: image = self.common.get_resource_path('images/share_in_progress.png') self.info_in_progress_downloads_count.setText(' {1:d}'.format(image, self.downloads_in_progress)) - self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.downloads_in_progress)) + self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip').format(self.downloads_in_progress)) @staticmethod def _compute_total_size(filenames): diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py index a34796f1..fe1e91b8 100644 --- a/onionshare_gui/share_mode/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -89,7 +89,7 @@ class Downloads(QtWidgets.QScrollArea): self.downloads = {} - self.setWindowTitle(strings._('gui_downloads', True)) + self.setWindowTitle(strings._('gui_downloads')) self.setWidgetResizable(True) self.setMinimumHeight(150) self.setMinimumWidth(350) @@ -98,10 +98,10 @@ class Downloads(QtWidgets.QScrollArea): self.vbar = self.verticalScrollBar() self.vbar.rangeChanged.connect(self.resizeScroll) - downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + 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', True)) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + 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() diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/share_mode/file_selection.py index 628ad5ef..f6f54469 100644 --- a/onionshare_gui/share_mode/file_selection.py +++ b/onionshare_gui/share_mode/file_selection.py @@ -41,7 +41,7 @@ class DropHereLabel(QtWidgets.QLabel): if image: self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) 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.hide() @@ -65,7 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) 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.hide() @@ -216,7 +216,7 @@ class FileList(QtWidgets.QListWidget): if filename not in filenames: 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 fileinfo = QtCore.QFileInfo(filename) @@ -301,9 +301,9 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # 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.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) + self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() @@ -340,7 +340,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ 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: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) diff --git a/onionshare_gui/tor_connection_dialog.py b/onionshare_gui/tor_connection_dialog.py index b3bd1fe5..2bcbf1a6 100644 --- a/onionshare_gui/tor_connection_dialog.py +++ b/onionshare_gui/tor_connection_dialog.py @@ -51,7 +51,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): self.setFixedSize(400, 150) # Label - self.setLabelText(strings._('connecting_to_tor', True)) + self.setLabelText(strings._('connecting_to_tor')) # Progress bar ticks from 0 to 100 self.setRange(0, 100) @@ -81,7 +81,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def _tor_status_update(self, progress, summary): self.setValue(int(progress)) - self.setLabelText("{}
    {}".format(strings._('connecting_to_tor', True), summary)) + self.setLabelText("{}
    {}".format(strings._('connecting_to_tor'), summary)) def _connected_to_tor(self): self.common.log('TorConnectionDialog', '_connected_to_tor') @@ -104,7 +104,7 @@ class TorConnectionDialog(QtWidgets.QProgressDialog): def alert_and_open_settings(): # 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 self.open_settings.emit() -- cgit v1.2.3-54-g00ecf From c10500148b415aaadb889c7a48bb87ad3f55b6da Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 18:19:25 -0700 Subject: Fix tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py tests --- tests_gui_local/commontests.py | 66 +++++++++++++++++++--- ...onshare_receive_mode_upload_test_public_mode.py | 57 ++----------------- tests_gui_tor/commontests.py | 20 +++---- tests_gui_tor/conftest.py | 5 +- 4 files changed, 75 insertions(+), 73 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index de1ad9ab..d3f8a100 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -3,11 +3,60 @@ import requests import socket import socks import zipfile +import json +import shutil from PyQt5 import QtCore, QtTest + from onionshare import strings +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 + 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): '''Test that the GUI actually is shown''' self.assertTrue(self.gui.show) @@ -32,7 +81,7 @@ class CommonTests(object): self.assertFalse(self.gui.share_mode.info_widget.isVisible()) def test_info_widget_is_visible(self, mode): - '''Test that the info widget along top of screen is shown''' + '''Test that the info widget along top of screen is shown''' if mode == 'receive': self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) if mode == 'share': @@ -69,9 +118,9 @@ class CommonTests(object): def test_server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) def test_settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' @@ -123,9 +172,9 @@ class CommonTests(object): def test_server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) def test_web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' @@ -195,12 +244,12 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if mode == 'receive': - 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 mode == 'share': if stay_open: - self.assertEquals(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: - self.assertEquals(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 def test_set_timeout(self, mode, timeout): @@ -304,4 +353,3 @@ class CommonTests(object): def test_add_button_visible(self): '''Test that the add button should be visible''' self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 30a290e7..7b1cfe79 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -8,6 +8,7 @@ import json from PyQt5 import QtWidgets from onionshare.common import Common +from onionshare.settings import Settings from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * @@ -18,65 +19,15 @@ class OnionShareGuiTest(unittest.TestCase): '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "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" + "receive_allow_receiver_shutdown": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py index a0d9bf5f..cb23cca9 100644 --- a/tests_gui_tor/commontests.py +++ b/tests_gui_tor/commontests.py @@ -32,7 +32,7 @@ class CommonTests(object): self.assertFalse(self.gui.share_mode.info_widget.isVisible()) def test_info_widget_is_visible(self, mode): - '''Test that the info widget along top of screen is shown''' + '''Test that the info widget along top of screen is shown''' if mode == 'receive': self.assertTrue(self.gui.receive_mode.info_widget.isVisible()) if mode == 'share': @@ -69,9 +69,9 @@ class CommonTests(object): def test_server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) def test_settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' @@ -126,9 +126,9 @@ class CommonTests(object): def test_server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) def test_web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' @@ -201,12 +201,12 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if mode == 'receive': - 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 mode == 'share': if stay_open: - self.assertEquals(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: - self.assertEquals(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')) def test_cancel_the_share(self, mode): '''Test that we can cancel this share before it's started up ''' @@ -354,6 +354,6 @@ class CommonTests(object): self.gui.app.onion.cleanup(stop_tor=True) QtTest.QTest.qWait(2500) 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': - 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')) diff --git a/tests_gui_tor/conftest.py b/tests_gui_tor/conftest.py index 8ac7efb8..23079e8d 100644 --- a/tests_gui_tor/conftest.py +++ b/tests_gui_tor/conftest.py @@ -151,7 +151,10 @@ def time_strftime(monkeypatch): @pytest.fixture def common_obj(): - return common.Common() + _common = common.Common() + _common.settings = settings.Settings(_common) + strings.load_strings(_common) + return _common @pytest.fixture def settings_obj(sys_onionshare_dev_mode, platform_linux): -- cgit v1.2.3-54-g00ecf From d18b77e85453ae269f2c634e549a2e46ffc2472d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 18:32:18 -0700 Subject: Fix the rest of the local GUI tests --- .../onionshare_receive_mode_upload_test.py | 57 ++-------------------- ...onshare_receive_mode_upload_test_public_mode.py | 1 - .../onionshare_share_mode_download_test.py | 55 ++------------------- ...onshare_share_mode_download_test_public_mode.py | 56 ++------------------- ...nionshare_share_mode_download_test_stay_open.py | 55 ++------------------- tests_gui_local/onionshare_slug_persistent_test.py | 56 ++------------------- tests_gui_local/onionshare_timer_test.py | 55 ++------------------- 7 files changed, 18 insertions(+), 317 deletions(-) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 2aa2ed94..cf439950 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -15,68 +15,17 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "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" + "receive_allow_receiver_shutdown": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 7b1cfe79..f89da51a 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -16,7 +16,6 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod def setUpClass(cls): test_settings = { diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index c546fb61..1800150f 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -15,66 +15,17 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "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" + "close_after_first_download": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index 764b5885..37c593f9 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -15,66 +15,16 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "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" + "public_mode": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index b92ff097..57ea8424 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -15,66 +15,17 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "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" + "close_after_first_download": False } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index 1e5614dc..54652eeb 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -15,68 +15,18 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - slug = '' - @classmethod 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 = { - "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, - "receive_allow_receiver_shutdown": True, - "save_private_key": True, - "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" + "save_private_key": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py index 1a5134e2..26eb4560 100644 --- a/tests_gui_local/onionshare_timer_test.py +++ b/tests_gui_local/onionshare_timer_test.py @@ -15,66 +15,17 @@ from onionshare_gui import * from .commontests import CommonTests class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' @classmethod 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 = { - "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, - "receive_allow_receiver_shutdown": 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" + "shutdown_timeout": True } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + cls.gui = CommonTests.set_up(test_settings) @classmethod def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') + CommonTests.tear_down() @pytest.mark.run(order=1) def test_gui_loaded(self): -- cgit v1.2.3-54-g00ecf From 907503952f8d49a57f6fc3e793b7104fcf6295f2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 30 Sep 2018 18:40:47 -0700 Subject: Oops, import strings into tor GUI tests --- tests_gui_tor/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_gui_tor/conftest.py b/tests_gui_tor/conftest.py index 23079e8d..3ae6fd52 100644 --- a/tests_gui_tor/conftest.py +++ b/tests_gui_tor/conftest.py @@ -8,7 +8,7 @@ import tempfile import pytest -from onionshare import common, web, settings +from onionshare import common, web, settings, strings @pytest.fixture def temp_dir_1024(): -- cgit v1.2.3-54-g00ecf From c43d882a65109f3e070bae6cd8c91fc140526fc0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 12:53:10 +1000 Subject: Explicitly set true/false values for settings that need it --- onionshare_gui/settings_dialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b9e0453a..794bd79c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -938,7 +938,7 @@ class SettingsDialog(QtWidgets.QDialog): if self.save_private_key_checkbox.isChecked(): # force the legacy mode on use_legacy_v2_onions = True - settings.set('save_private_key') + settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) settings.set('hidservauth_string', self.old_settings.get('hidservauth_string')) @@ -950,7 +950,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('hidservauth_string', '') if use_legacy_v2_onions: - settings.set('use_legacy_v2_onions') + settings.set('use_legacy_v2_onions', True) else: settings.set('use_legacy_v2_onions', False) # If we are not using legacy mode, but we previously had persistence turned on, force it off! @@ -984,7 +984,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('connection_type', 'socket_file') if self.autoupdate_checkbox.isChecked(): - settings.set('use_autoupdate') + settings.set('use_autoupdate', True) else: settings.set('use_autoupdate', False) @@ -1004,19 +1004,19 @@ class SettingsDialog(QtWidgets.QDialog): # Whether we use bridges if self.tor_bridges_no_bridges_radio.isChecked(): - settings.set('no_bridges') + settings.set('no_bridges', True) settings.set('tor_bridges_use_obfs4', False) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_obfs4_radio.isChecked(): settings.set('no_bridges', False) - settings.set('tor_bridges_use_obfs4') + settings.set('tor_bridges_use_obfs4', True) settings.set('tor_bridges_use_meek_lite_azure', False) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_meek_lite_azure_radio.isChecked(): settings.set('no_bridges', False) settings.set('tor_bridges_use_obfs4', False) - settings.set('tor_bridges_use_meek_lite_azure') + settings.set('tor_bridges_use_meek_lite_azure', True) settings.set('tor_bridges_use_custom_bridges', '') if self.tor_bridges_use_custom_radio.isChecked(): settings.set('no_bridges', False) @@ -1048,7 +1048,7 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('tor_bridges_use_custom_bridges', new_bridges) else: Alert(self.common, strings._('gui_settings_tor_bridges_invalid')) - settings.set('no_bridges') + settings.set('no_bridges', True) return False return settings -- cgit v1.2.3-54-g00ecf From 29b1b97282b0167f275ca36eba2a69ca46f21663 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 12:53:23 +1000 Subject: Add French translation for restarting OnionShare to see change in language take effect --- share/locale/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 967e456e..ee206662 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -29,5 +29,6 @@ "gui_please_wait": "Attendez-vous...", "gui_quit_warning_quit": "Quitter", "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" } -- cgit v1.2.3-54-g00ecf From 9aa9dc39a1d83992665746f722f3d279046ac1fd Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 13:32:09 +1000 Subject: Move Alert dialog about restart into the conditional that fires only if locale was changed --- onionshare_gui/settings_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 794bd79c..5a81282a 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -850,7 +850,7 @@ class SettingsDialog(QtWidgets.QDialog): 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) + Alert(self.common, notice, QtWidgets.QMessageBox.Information) # Save the new settings settings.save() -- cgit v1.2.3-54-g00ecf From 7e8a76be8ae716d49c3c1f4a996b175361afcd97 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 15:32:53 +1000 Subject: Load default settings before parsing for any alternate config. Reload strings if an alternate config was passed in after --- onionshare/__init__.py | 10 ++++++++-- onionshare_gui/__init__.py | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 2f0ef14c..069559c5 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -33,8 +33,12 @@ def main(cwd=None): """ common = Common() - # Load settings right away, in order to get locale setting for strings - common.load_settings(config) + # Load the default settings and strings early, for the sake of being able to parse options. + # These won't be in the user's chosen locale necessarily, but we need to parse them + # early in order to even display the option to pass alternate settings (which might + # contain a preferred locale). + # If an alternate --config is passed, we'll reload strings later. + common.load_settings() strings.load_strings(common) # Display OnionShare banner @@ -95,6 +99,8 @@ def main(cwd=None): # Re-load settings, if a custom config was passed in if config: common.load_settings(config) + # Re-load the strings, in case the provided config has changed locale + strings.load_strings(common) # Debug mode? common.debug = debug diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py index b5520071..675bb52d 100644 --- a/onionshare_gui/__init__.py +++ b/onionshare_gui/__init__.py @@ -59,7 +59,11 @@ def main(): common = Common() common.define_css() - # Load settings right away, in order to get locale setting for strings + # 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) @@ -88,6 +92,10 @@ def main(): filenames[i] = os.path.abspath(filenames[i]) config = args.config + if config: + # Re-load the strings, in case the provided config has changed locale + common.load_settings(config) + strings.load_strings(common) local_only = bool(args.local_only) debug = bool(args.debug) -- cgit v1.2.3-54-g00ecf From 3f32db2ccac6007255bff5eb0afad4fe11582389 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 18:42:53 +1000 Subject: Fix logic for handling an upload still in progress when timer runs out. Show thankyou page for last uploader post-timer expiry --- onionshare/web/receive_mode.py | 74 ++++++++++++++++++++------------- onionshare_gui/receive_mode/__init__.py | 8 +++- share/templates/closed.html | 22 ---------- share/templates/thankyou.html | 22 ++++++++++ 4 files changed, 75 insertions(+), 51 deletions(-) delete mode 100644 share/templates/closed.html create mode 100644 share/templates/thankyou.html diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 73762573..5035f68a 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -19,6 +19,7 @@ class ReceiveModeWeb(object): self.web = web self.can_upload = True + self.can_stop_share_now = False self.upload_count = 0 self.uploads_in_progress = [] @@ -39,6 +40,7 @@ class ReceiveModeWeb(object): r = make_response(render_template( 'receive.html', upload_action=upload_action)) + return self.web.add_security_headers(r) @self.web.app.route("/") @@ -138,10 +140,24 @@ class ReceiveModeWeb(object): for filename in filenames: flash('Sent {}'.format(filename), 'info') - if self.common.settings.get('public_mode'): - return redirect('/') + if self.can_upload: + if self.common.settings.get('public_mode'): + path = '/' + else: + path = '/{}'.format(slug_candidate) + + return redirect('{}'.format(path)) else: - return redirect('/{}'.format(slug_candidate)) + # It was the last upload and the timer ran out + if self.common.settings.get('public_mode'): + return thankyou_logic(slug_candidate) + else: + return thankyou_logic() + + def thankyou_logic(slug_candidate=''): + r = make_response(render_template( + 'thankyou.html')) + return self.web.add_security_headers(r) @self.web.app.route("//upload", methods=['POST']) def upload(slug_candidate): @@ -231,39 +247,36 @@ class ReceiveModeRequest(Request): if self.path == '/upload': self.upload_request = True - if self.upload_request: + if self.upload_request and self.web.receive_mode.can_upload: # A dictionary that maps filenames to the bytes uploaded so far self.progress = {} # Create an upload_id, attach it to the request self.upload_id = self.web.receive_mode.upload_count - if self.web.receive_mode.can_upload: - self.web.receive_mode.upload_count += 1 + self.web.receive_mode.upload_count += 1 - # Figure out the content length - try: - self.content_length = int(self.headers['Content-Length']) - except: - self.content_length = 0 + # Figure out the content length + try: + self.content_length = int(self.headers['Content-Length']) + except: + self.content_length = 0 - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) + print("{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) + )) - # append to self.uploads_in_progress - self.web.receive_mode.uploads_in_progress.append(self.upload_id) + # append to self.uploads_in_progress + self.web.receive_mode.uploads_in_progress.append(self.upload_id) - # Tell the GUI - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, - 'content_length': self.content_length - }) + # Tell the GUI + self.web.add_request(self.web.REQUEST_STARTED, self.path, { + 'id': self.upload_id, + 'content_length': self.content_length + }) - self.previous_file = None - else: - self.upload_rejected = True + self.previous_file = None def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): """ @@ -284,14 +297,19 @@ class ReceiveModeRequest(Request): """ super(ReceiveModeRequest, self).close() if self.upload_request: - if not self.upload_rejected: + try: + upload_id = self.upload_id # Inform the GUI that the upload has finished self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': self.upload_id + 'id': upload_id }) # remove from self.uploads_in_progress - self.web.receive_mode.uploads_in_progress.remove(self.upload_id) + self.web.receive_mode.uploads_in_progress.remove(upload_id) + + except AttributeError: + # We may not have got an upload_id (e.g uploads were rejected) + pass def file_write_func(self, filename, length): """ diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py index faa65ffe..c10622e4 100644 --- a/onionshare_gui/receive_mode/__init__.py +++ b/onionshare_gui/receive_mode/__init__.py @@ -51,6 +51,7 @@ class ReceiveMode(Mode): self.uploads_in_progress = 0 self.uploads_completed = 0 self.new_upload = False # For scrolling to the bottom of the uploads list + self.can_stop_server = False # for communicating to the auto-stop timer # Information about share, and show uploads button self.info_in_progress_uploads_count = QtWidgets.QLabel() @@ -93,7 +94,7 @@ class ReceiveMode(Mode): The shutdown timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress: + if self.web.receive_mode.upload_count == 0 or self.can_stop_server: self.server_status.stop_server() self.server_status_label.setText(strings._('close_on_timeout', True)) return True @@ -110,7 +111,9 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters + self.can_stop_server = False self.web.receive_mode.upload_count = 0 + self.web.receive_mode.can_upload = True self.web.error404_count = 0 # Hide and reset the uploads if we have previously shared @@ -144,6 +147,7 @@ class ReceiveMode(Mode): self.uploads.add(event["data"]["id"], event["data"]["content_length"]) self.uploads_in_progress += 1 self.update_uploads_in_progress() + self.can_stop_server = False self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) @@ -177,6 +181,8 @@ class ReceiveMode(Mode): # Update the 'in progress uploads' info self.uploads_in_progress -= 1 self.update_uploads_in_progress() + if self.uploads_in_progress == 0: + self.can_stop_server = True def on_reload_settings(self): """ diff --git a/share/templates/closed.html b/share/templates/closed.html deleted file mode 100644 index 64c8b369..00000000 --- a/share/templates/closed.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - OnionShare is closed - - - - -
    - -

    OnionShare

    -
    - -
    -
    -

    -

    Thank you for using OnionShare

    -

    You may now close this window.

    -
    -
    - - diff --git a/share/templates/thankyou.html b/share/templates/thankyou.html new file mode 100644 index 00000000..64c8b369 --- /dev/null +++ b/share/templates/thankyou.html @@ -0,0 +1,22 @@ + + + + OnionShare is closed + + + + +
    + +

    OnionShare

    +
    + +
    +
    +

    +

    Thank you for using OnionShare

    +

    You may now close this window.

    +
    +
    + + -- cgit v1.2.3-54-g00ecf From d104af11dce68d85c8c4cc2b5796af24040b99ba Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 19:15:58 +1000 Subject: remove unused variable, whitespace --- onionshare/web/receive_mode.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 5035f68a..7fa274d5 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -19,7 +19,6 @@ class ReceiveModeWeb(object): self.web = web self.can_upload = True - self.can_stop_share_now = False self.upload_count = 0 self.uploads_in_progress = [] @@ -40,7 +39,6 @@ class ReceiveModeWeb(object): r = make_response(render_template( 'receive.html', upload_action=upload_action)) - return self.web.add_security_headers(r) @self.web.app.route("/") -- cgit v1.2.3-54-g00ecf From 9e14514d25a864c154cbf17e70b64cffbd8631a7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 19:17:50 +1000 Subject: Another unused variable --- onionshare/web/receive_mode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 7fa274d5..8602e98a 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -236,7 +236,6 @@ class ReceiveModeRequest(Request): # Is this a valid upload request? self.upload_request = False - self.upload_rejected = False if self.method == 'POST': if self.path == '/{}/upload'.format(self.web.slug): self.upload_request = True -- cgit v1.2.3-54-g00ecf From 7d140f384ff64952f1c656cb8c349a284fa312a0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 1 Oct 2018 19:18:50 +1000 Subject: remove uploads_in_progress list from web side --- onionshare/web/receive_mode.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 8602e98a..bc5e1734 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -20,7 +20,6 @@ class ReceiveModeWeb(object): self.can_upload = True self.upload_count = 0 - self.uploads_in_progress = [] self.define_routes() @@ -264,9 +263,6 @@ class ReceiveModeRequest(Request): strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) )) - # append to self.uploads_in_progress - self.web.receive_mode.uploads_in_progress.append(self.upload_id) - # Tell the GUI self.web.add_request(self.web.REQUEST_STARTED, self.path, { 'id': self.upload_id, @@ -301,9 +297,6 @@ class ReceiveModeRequest(Request): 'id': upload_id }) - # remove from self.uploads_in_progress - self.web.receive_mode.uploads_in_progress.remove(upload_id) - except AttributeError: # We may not have got an upload_id (e.g uploads were rejected) pass -- cgit v1.2.3-54-g00ecf From e9148ddb49a982033eee575d19bbc741e0616b48 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 2 Oct 2018 07:33:13 +1000 Subject: remove unused variable --- onionshare/web/web.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 2885e87f..45d021c0 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -66,8 +66,6 @@ class Web(object): # Use a custom Request class to track upload progess self.app.request_class = ReceiveModeRequest - self.can_upload = True - # Starting in Flask 0.11, render_template_string autoescapes template variables # by default. To prevent content injection through template variables in # earlier versions of Flask, we force autoescaping in the Jinja2 template -- cgit v1.2.3-54-g00ecf From 61d2e6cc5f70f9761b2988d9bb5c1a4e52593a86 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 2 Oct 2018 08:22:08 +1000 Subject: Try to fix logic handling last upload after timer expiry --- onionshare/web/receive_mode.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index bc5e1734..60909a23 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -243,7 +243,11 @@ class ReceiveModeRequest(Request): if self.path == '/upload': self.upload_request = True - if self.upload_request and self.web.receive_mode.can_upload: + # Prevent new uploads if we've said so (timer expired) + if not self.web.receive_mode.can_upload: + self.upload_request = False + + if self.upload_request: # A dictionary that maps filenames to the bytes uploaded so far self.progress = {} @@ -290,16 +294,11 @@ class ReceiveModeRequest(Request): """ super(ReceiveModeRequest, self).close() if self.upload_request: - try: - upload_id = self.upload_id - # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': upload_id - }) - - except AttributeError: - # We may not have got an upload_id (e.g uploads were rejected) - pass + # Inform the GUI that the upload has finished + self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': self.upload_id + }) + def file_write_func(self, filename, length): """ -- cgit v1.2.3-54-g00ecf From 875b538347a421f23b6ef5f667f68b74d31984ed Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 2 Oct 2018 15:41:29 +1000 Subject: Make auto-stop timer work on CLI when an upload is still in progress on expiry --- onionshare/__init__.py | 7 +++++++ onionshare/web/receive_mode.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 715c5571..42294ec1 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -198,6 +198,13 @@ def main(cwd=None): print(strings._("close_on_timeout")) web.stop(app.port) break + if mode == 'receive': + if web.receive_mode.upload_count == 0 or not web.receive_mode.uploads_in_progress: + print(strings._("close_on_timeout")) + web.stop(app.port) + break + else: + web.receive_mode.can_upload = False # Allow KeyboardInterrupt exception to be handled with threads # https://stackoverflow.com/questions/3788208/python-threading-ignores-keyboardinterrupt-exception time.sleep(0.2) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 60909a23..4ea95201 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -20,6 +20,7 @@ class ReceiveModeWeb(object): self.can_upload = True self.upload_count = 0 + self.uploads_in_progress = [] self.define_routes() @@ -273,6 +274,8 @@ class ReceiveModeRequest(Request): 'content_length': self.content_length }) + self.web.receive_mode.uploads_in_progress.append(self.upload_id) + self.previous_file = None def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): @@ -298,6 +301,7 @@ class ReceiveModeRequest(Request): self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { 'id': self.upload_id }) + self.web.receive_mode.uploads_in_progress.remove(self.upload_id) def file_write_func(self, filename, length): -- cgit v1.2.3-54-g00ecf From 7c61483ae9adea7e5b78bc45249bba8afb9efc92 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 14:48:15 -0700 Subject: Move Mode module into its own folder --- onionshare_gui/mode.py | 354 ---------------------------------------- onionshare_gui/mode/__init__.py | 354 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 354 deletions(-) delete mode 100644 onionshare_gui/mode.py create mode 100644 onionshare_gui/mode/__init__.py diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py deleted file mode 100644 index 1a961149..00000000 --- a/onionshare_gui/mode.py +++ /dev/null @@ -1,354 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from onionshare.common import ShutdownTimer - -from .server_status import ServerStatus -from .threads import OnionThread -from .widgets import Alert - -class Mode(QtWidgets.QWidget): - """ - The class that ShareMode and ReceiveMode inherit from. - """ - start_server_finished = QtCore.pyqtSignal() - stop_server_finished = QtCore.pyqtSignal() - starting_server_step2 = QtCore.pyqtSignal() - starting_server_step3 = QtCore.pyqtSignal() - starting_server_error = QtCore.pyqtSignal(str) - set_server_active = QtCore.pyqtSignal(bool) - adjust_size = QtCore.pyqtSignal(int) - - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): - super(Mode, self).__init__() - self.common = common - self.qtapp = qtapp - self.app = app - - self.status_bar = status_bar - self.server_status_label = server_status_label - self.system_tray = system_tray - - self.filenames = filenames - - self.setMinimumWidth(self.common.min_window_width) - - # The web object gets created in init() - self.web = None - - # Local mode is passed from OnionShareGui - self.local_only = local_only - - # Threads start out as None - self.onion_thread = None - self.web_thread = None - - # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) - self.server_status.server_started.connect(self.start_server) - self.server_status.server_stopped.connect(self.stop_server) - self.server_status.server_canceled.connect(self.cancel_server) - self.start_server_finished.connect(self.server_status.start_server_finished) - self.stop_server_finished.connect(self.server_status.stop_server_finished) - self.starting_server_step2.connect(self.start_server_step2) - self.starting_server_step3.connect(self.start_server_step3) - self.starting_server_error.connect(self.start_server_error) - - # Primary action - # Note: It's up to the downstream Mode to add this to its layout - self.primary_action_layout = QtWidgets.QVBoxLayout() - self.primary_action_layout.addWidget(self.server_status) - self.primary_action = QtWidgets.QWidget() - self.primary_action.setLayout(self.primary_action_layout) - - # Hack to allow a minimum width on the main layout - # Note: It's up to the downstream Mode to add this to its layout - self.min_width_widget = QtWidgets.QWidget() - self.min_width_widget.setMinimumWidth(self.common.min_window_width) - - def init(self): - """ - Add custom initialization here. - """ - pass - - def timer_callback(self): - """ - This method is called regularly on a timer. - """ - # If the auto-shutdown timer has stopped, stop the server - if self.server_status.status == ServerStatus.STATUS_STARTED: - if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): - if self.timeout > 0: - now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.timeout) - - # Update the server button - server_button_text = self.get_stop_server_shutdown_timeout_text() - self.server_status.server_button.setText(server_button_text.format(seconds_remaining)) - - self.status_bar.clearMessage() - if not self.app.shutdown_timer.is_alive(): - if self.timeout_finished_should_stop_server(): - self.server_status.stop_server() - - def timer_callback_custom(self): - """ - Add custom timer code. - """ - pass - - def get_stop_server_shutdown_timeout_text(self): - """ - Return the string to put on the stop server button, if there's a shutdown timeout - """ - pass - - def timeout_finished_should_stop_server(self): - """ - The shutdown timer expired, should we stop the server? Returns a bool - """ - pass - - def start_server(self): - """ - Start the onionshare server. This uses multiple threads to start the Tor onion - server and the web app. - """ - self.common.log('Mode', 'start_server') - - self.start_server_custom() - - self.set_server_active.emit(True) - self.app.set_stealth(self.common.settings.get('use_stealth')) - - # Clear the status bar - self.status_bar.clearMessage() - self.server_status_label.setText('') - - self.common.log('Mode', 'start_server', 'Starting an onion thread') - self.onion_thread = OnionThread(self) - self.onion_thread.success.connect(self.starting_server_step2.emit) - self.onion_thread.error.connect(self.starting_server_error.emit) - self.onion_thread.start() - - def start_server_custom(self): - """ - Add custom initialization here. - """ - pass - - def start_server_step2(self): - """ - Step 2 in starting the onionshare server. - """ - self.common.log('Mode', 'start_server_step2') - - self.start_server_step2_custom() - - # Nothing to do here. - - # start_server_step2_custom has call these to move on: - # self.starting_server_step3.emit() - # self.start_server_finished.emit() - - def start_server_step2_custom(self): - """ - Add custom initialization here. - """ - pass - - def start_server_step3(self): - """ - Step 3 in starting the onionshare server. - """ - self.common.log('Mode', 'start_server_step3') - - self.start_server_step3_custom() - - if self.common.settings.get('shutdown_timeout'): - # Convert the date value to seconds between now and then - now = QtCore.QDateTime.currentDateTime() - self.timeout = now.secsTo(self.server_status.timeout) - # Set the shutdown timeout value - if self.timeout > 0: - self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) - self.app.shutdown_timer.start() - # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. - else: - self.stop_server() - self.start_server_error(strings._('gui_server_started_after_timeout')) - - def start_server_step3_custom(self): - """ - Add custom initialization here. - """ - pass - - def start_server_error(self, error): - """ - If there's an error when trying to start the onion service - """ - self.common.log('Mode', 'start_server_error') - - Alert(self.common, error, QtWidgets.QMessageBox.Warning) - self.set_server_active.emit(False) - self.server_status.stop_server() - self.status_bar.clearMessage() - - self.start_server_error_custom() - - def start_server_error_custom(self): - """ - Add custom initialization here. - """ - pass - - def cancel_server(self): - """ - Cancel the server while it is preparing to start - """ - self.cancel_server_custom() - - if self.onion_thread: - self.common.log('Mode', 'cancel_server: quitting onion thread') - self.onion_thread.quit() - if self.web_thread: - self.common.log('Mode', 'cancel_server: quitting web thread') - self.web_thread.quit() - self.stop_server() - - def cancel_server_custom(self): - """ - Add custom initialization here. - """ - pass - - def stop_server(self): - """ - Stop the onionshare server. - """ - self.common.log('Mode', 'stop_server') - - if self.server_status.status != ServerStatus.STATUS_STOPPED: - try: - self.web.stop(self.app.port) - except: - # Probably we had no port to begin with (Onion service didn't start) - pass - self.app.cleanup() - - self.stop_server_custom() - - self.set_server_active.emit(False) - self.stop_server_finished.emit() - - def stop_server_custom(self): - """ - Add custom initialization here. - """ - pass - - def handle_tor_broke(self): - """ - Handle connection from Tor breaking. - """ - if self.server_status.status != ServerStatus.STATUS_STOPPED: - self.server_status.stop_server() - self.handle_tor_broke_custom() - - def handle_tor_broke_custom(self): - """ - Add custom initialization here. - """ - pass - - # Handle web server events - - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - pass - - def handle_request_started(self, event): - """ - Handle REQUEST_STARTED event. - """ - pass - - def handle_request_rate_limit(self, event): - """ - Handle REQUEST_RATE_LIMIT event. - """ - self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - pass - - def handle_request_canceled(self, event): - """ - Handle REQUEST_CANCELED event. - """ - pass - - def handle_request_close_server(self, event): - """ - Handle REQUEST_CLOSE_SERVER event. - """ - pass - - def handle_request_upload_file_renamed(self, event): - """ - Handle REQUEST_UPLOAD_FILE_RENAMED event. - """ - pass - - def handle_request_upload_finished(self, event): - """ - Handle REQUEST_UPLOAD_FINISHED event. - """ - pass - - def resize_window(self): - """ - We call this to force the OnionShare window to resize itself to be smaller. - For this to do anything, the Mode needs to override it and call: - - self.adjust_size.emit(min_width) - - It can calculate min_width (the new minimum window width) based on what - widgets are visible. - """ - pass - - def show(self): - """ - Always resize the window after showing this Mode widget. - """ - super(Mode, self).show() - self.qtapp.processEvents() - self.resize_window() diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py new file mode 100644 index 00000000..cfbb235b --- /dev/null +++ b/onionshare_gui/mode/__init__.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.common import ShutdownTimer + +from ..server_status import ServerStatus +from ..threads import OnionThread +from ..widgets import Alert + +class Mode(QtWidgets.QWidget): + """ + The class that ShareMode and ReceiveMode inherit from. + """ + start_server_finished = QtCore.pyqtSignal() + stop_server_finished = QtCore.pyqtSignal() + starting_server_step2 = QtCore.pyqtSignal() + starting_server_step3 = QtCore.pyqtSignal() + starting_server_error = QtCore.pyqtSignal(str) + set_server_active = QtCore.pyqtSignal(bool) + adjust_size = QtCore.pyqtSignal(int) + + def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): + super(Mode, self).__init__() + self.common = common + self.qtapp = qtapp + self.app = app + + self.status_bar = status_bar + self.server_status_label = server_status_label + self.system_tray = system_tray + + self.filenames = filenames + + self.setMinimumWidth(self.common.min_window_width) + + # The web object gets created in init() + self.web = None + + # Local mode is passed from OnionShareGui + self.local_only = local_only + + # Threads start out as None + self.onion_thread = None + self.web_thread = None + + # Server status + self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) + self.server_status.server_started.connect(self.start_server) + self.server_status.server_stopped.connect(self.stop_server) + self.server_status.server_canceled.connect(self.cancel_server) + self.start_server_finished.connect(self.server_status.start_server_finished) + self.stop_server_finished.connect(self.server_status.stop_server_finished) + self.starting_server_step2.connect(self.start_server_step2) + self.starting_server_step3.connect(self.start_server_step3) + self.starting_server_error.connect(self.start_server_error) + + # Primary action + # Note: It's up to the downstream Mode to add this to its layout + self.primary_action_layout = QtWidgets.QVBoxLayout() + self.primary_action_layout.addWidget(self.server_status) + self.primary_action = QtWidgets.QWidget() + self.primary_action.setLayout(self.primary_action_layout) + + # Hack to allow a minimum width on the main layout + # Note: It's up to the downstream Mode to add this to its layout + self.min_width_widget = QtWidgets.QWidget() + self.min_width_widget.setMinimumWidth(self.common.min_window_width) + + def init(self): + """ + Add custom initialization here. + """ + pass + + def timer_callback(self): + """ + This method is called regularly on a timer. + """ + # If the auto-shutdown timer has stopped, stop the server + if self.server_status.status == ServerStatus.STATUS_STARTED: + if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'): + if self.timeout > 0: + now = QtCore.QDateTime.currentDateTime() + seconds_remaining = now.secsTo(self.server_status.timeout) + + # Update the server button + server_button_text = self.get_stop_server_shutdown_timeout_text() + self.server_status.server_button.setText(server_button_text.format(seconds_remaining)) + + self.status_bar.clearMessage() + if not self.app.shutdown_timer.is_alive(): + if self.timeout_finished_should_stop_server(): + self.server_status.stop_server() + + def timer_callback_custom(self): + """ + Add custom timer code. + """ + pass + + def get_stop_server_shutdown_timeout_text(self): + """ + Return the string to put on the stop server button, if there's a shutdown timeout + """ + pass + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + pass + + def start_server(self): + """ + Start the onionshare server. This uses multiple threads to start the Tor onion + server and the web app. + """ + self.common.log('Mode', 'start_server') + + self.start_server_custom() + + self.set_server_active.emit(True) + self.app.set_stealth(self.common.settings.get('use_stealth')) + + # Clear the status bar + self.status_bar.clearMessage() + self.server_status_label.setText('') + + self.common.log('Mode', 'start_server', 'Starting an onion thread') + self.onion_thread = OnionThread(self) + self.onion_thread.success.connect(self.starting_server_step2.emit) + self.onion_thread.error.connect(self.starting_server_error.emit) + self.onion_thread.start() + + def start_server_custom(self): + """ + Add custom initialization here. + """ + pass + + def start_server_step2(self): + """ + Step 2 in starting the onionshare server. + """ + self.common.log('Mode', 'start_server_step2') + + self.start_server_step2_custom() + + # Nothing to do here. + + # start_server_step2_custom has call these to move on: + # self.starting_server_step3.emit() + # self.start_server_finished.emit() + + def start_server_step2_custom(self): + """ + Add custom initialization here. + """ + pass + + def start_server_step3(self): + """ + Step 3 in starting the onionshare server. + """ + self.common.log('Mode', 'start_server_step3') + + self.start_server_step3_custom() + + if self.common.settings.get('shutdown_timeout'): + # Convert the date value to seconds between now and then + now = QtCore.QDateTime.currentDateTime() + self.timeout = now.secsTo(self.server_status.timeout) + # Set the shutdown timeout value + if self.timeout > 0: + self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout) + self.app.shutdown_timer.start() + # The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start. + else: + self.stop_server() + self.start_server_error(strings._('gui_server_started_after_timeout')) + + def start_server_step3_custom(self): + """ + Add custom initialization here. + """ + pass + + def start_server_error(self, error): + """ + If there's an error when trying to start the onion service + """ + self.common.log('Mode', 'start_server_error') + + Alert(self.common, error, QtWidgets.QMessageBox.Warning) + self.set_server_active.emit(False) + self.server_status.stop_server() + self.status_bar.clearMessage() + + self.start_server_error_custom() + + def start_server_error_custom(self): + """ + Add custom initialization here. + """ + pass + + def cancel_server(self): + """ + Cancel the server while it is preparing to start + """ + self.cancel_server_custom() + + if self.onion_thread: + self.common.log('Mode', 'cancel_server: quitting onion thread') + self.onion_thread.quit() + if self.web_thread: + self.common.log('Mode', 'cancel_server: quitting web thread') + self.web_thread.quit() + self.stop_server() + + def cancel_server_custom(self): + """ + Add custom initialization here. + """ + pass + + def stop_server(self): + """ + Stop the onionshare server. + """ + self.common.log('Mode', 'stop_server') + + if self.server_status.status != ServerStatus.STATUS_STOPPED: + try: + self.web.stop(self.app.port) + except: + # Probably we had no port to begin with (Onion service didn't start) + pass + self.app.cleanup() + + self.stop_server_custom() + + self.set_server_active.emit(False) + self.stop_server_finished.emit() + + def stop_server_custom(self): + """ + Add custom initialization here. + """ + pass + + def handle_tor_broke(self): + """ + Handle connection from Tor breaking. + """ + if self.server_status.status != ServerStatus.STATUS_STOPPED: + self.server_status.stop_server() + self.handle_tor_broke_custom() + + def handle_tor_broke_custom(self): + """ + Add custom initialization here. + """ + pass + + # Handle web server events + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + pass + + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + pass + + def handle_request_rate_limit(self, event): + """ + Handle REQUEST_RATE_LIMIT event. + """ + self.stop_server() + Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + pass + + def handle_request_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + pass + + def handle_request_close_server(self, event): + """ + Handle REQUEST_CLOSE_SERVER event. + """ + pass + + def handle_request_upload_file_renamed(self, event): + """ + Handle REQUEST_UPLOAD_FILE_RENAMED event. + """ + pass + + def handle_request_upload_finished(self, event): + """ + Handle REQUEST_UPLOAD_FINISHED event. + """ + pass + + def resize_window(self): + """ + We call this to force the OnionShare window to resize itself to be smaller. + For this to do anything, the Mode needs to override it and call: + + self.adjust_size.emit(min_width) + + It can calculate min_width (the new minimum window width) based on what + widgets are visible. + """ + pass + + def show(self): + """ + Always resize the window after showing this Mode widget. + """ + super(Mode, self).show() + self.qtapp.processEvents() + self.resize_window() -- cgit v1.2.3-54-g00ecf From 801d8b965c693b310a8c4ec5e419564577041b69 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 14:54:51 -0700 Subject: Move ShareMode and ReceiveMode into Mode module --- onionshare_gui/mode/receive_mode/__init__.py | 200 ++++++++++++ onionshare_gui/mode/receive_mode/info.py | 136 ++++++++ onionshare_gui/mode/receive_mode/uploads.py | 395 +++++++++++++++++++++++ onionshare_gui/mode/share_mode/__init__.py | 378 ++++++++++++++++++++++ onionshare_gui/mode/share_mode/downloads.py | 248 ++++++++++++++ onionshare_gui/mode/share_mode/file_selection.py | 390 ++++++++++++++++++++++ onionshare_gui/mode/share_mode/info.py | 149 +++++++++ onionshare_gui/mode/share_mode/threads.py | 60 ++++ onionshare_gui/onionshare_gui.py | 4 +- onionshare_gui/receive_mode/__init__.py | 200 ------------ onionshare_gui/receive_mode/info.py | 136 -------- onionshare_gui/receive_mode/uploads.py | 395 ----------------------- onionshare_gui/share_mode/__init__.py | 378 ---------------------- onionshare_gui/share_mode/downloads.py | 248 -------------- onionshare_gui/share_mode/file_selection.py | 390 ---------------------- onionshare_gui/share_mode/info.py | 149 --------- onionshare_gui/share_mode/threads.py | 60 ---- setup.py | 5 +- 18 files changed, 1961 insertions(+), 1960 deletions(-) create mode 100644 onionshare_gui/mode/receive_mode/__init__.py create mode 100644 onionshare_gui/mode/receive_mode/info.py create mode 100644 onionshare_gui/mode/receive_mode/uploads.py create mode 100644 onionshare_gui/mode/share_mode/__init__.py create mode 100644 onionshare_gui/mode/share_mode/downloads.py create mode 100644 onionshare_gui/mode/share_mode/file_selection.py create mode 100644 onionshare_gui/mode/share_mode/info.py create mode 100644 onionshare_gui/mode/share_mode/threads.py delete mode 100644 onionshare_gui/receive_mode/__init__.py delete mode 100644 onionshare_gui/receive_mode/info.py delete mode 100644 onionshare_gui/receive_mode/uploads.py delete mode 100644 onionshare_gui/share_mode/__init__.py delete mode 100644 onionshare_gui/share_mode/downloads.py delete mode 100644 onionshare_gui/share_mode/file_selection.py delete mode 100644 onionshare_gui/share_mode/info.py delete mode 100644 onionshare_gui/share_mode/threads.py diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py new file mode 100644 index 00000000..96c76dbf --- /dev/null +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.web import Web + +from .uploads import Uploads +from .info import ReceiveModeInfo +from .. import Mode + +class ReceiveMode(Mode): + """ + Parts of the main window UI for receiving files. + """ + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Create the Web object + self.web = Web(self.common, True, 'receive') + + # Server status + self.server_status.set_mode('receive') + self.server_status.server_started_finished.connect(self.update_primary_action) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.update_primary_action) + + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Uploads + self.uploads = Uploads(self.common) + self.uploads.hide() + self.uploads_in_progress = 0 + self.uploads_completed = 0 + self.new_upload = False # For scrolling to the bottom of the uploads list + + # Information about share, and show uploads button + self.info = ReceiveModeInfo(self.common, self) + self.info.show_less() + + # Receive mode info + self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) + self.receive_info.setMinimumHeight(80) + self.receive_info.setWordWrap(True) + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addWidget(self.info) + self.main_layout.addWidget(self.receive_info) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addStretch() + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.uploads) + self.setLayout(self.wrapper_layout) + + def get_stop_server_shutdown_timeout_text(self): + """ + 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) + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + # TODO: wait until the final upload is done before stoppign the server? + return True + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.receive_mode.upload_count = 0 + self.web.error404_count = 0 + + # Hide and reset the uploads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. + """ + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + self.info.show_less() + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) + + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + self.uploads.add(event["data"]["id"], event["data"]["content_length"]) + self.info.update_indicator(True) + self.uploads_in_progress += 1 + self.info.update_uploads_in_progress() + + self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.uploads.update(event["data"]["id"], event["data"]["progress"]) + + def handle_request_close_server(self, event): + """ + Handle REQUEST_CLOSE_SERVER event. + """ + self.stop_server() + self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) + + def handle_request_upload_file_renamed(self, event): + """ + Handle REQUEST_UPLOAD_FILE_RENAMED event. + """ + self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"]) + + def handle_request_upload_finished(self, event): + """ + Handle REQUEST_UPLOAD_FINISHED event. + """ + self.uploads.finished(event["data"]["id"]) + # Update the total 'completed uploads' info + self.uploads_completed += 1 + self.info.update_uploads_completed() + # Update the 'in progress uploads' info + self.uploads_in_progress -= 1 + self.info.update_uploads_in_progress() + + def on_reload_settings(self): + """ + We should be ok to re-enable the 'Start Receive Mode' button now. + """ + self.primary_action.show() + self.info.show_more() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.uploads_completed = 0 + self.uploads_in_progress = 0 + self.info.update_uploads_completed() + self.info.update_uploads_in_progress() + self.uploads.reset() + + def update_primary_action(self): + self.common.log('ReceiveMode', 'update_primary_action') + + # Show the info widget when the server is active + if self.server_status.status == self.server_status.STATUS_STARTED: + self.info.show_more() + else: + self.info.show_less() + + # Resize window + self.resize_window() + + def resize_window(self): + min_width = self.common.min_window_width + if self.uploads.isVisible(): + min_width += 300 + self.adjust_size.emit(min_width) diff --git a/onionshare_gui/mode/receive_mode/info.py b/onionshare_gui/mode/receive_mode/info.py new file mode 100644 index 00000000..c23f8496 --- /dev/null +++ b/onionshare_gui/mode/receive_mode/info.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class ReceiveModeInfo(QtWidgets.QWidget): + """ + Receive mode information widget + """ + def __init__(self, common, receive_mode): + super(ReceiveModeInfo, self).__init__() + self.common = common + self.receive_mode = receive_mode + + # In progress and completed labels + self.in_progress_uploads_count = QtWidgets.QLabel() + self.in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) + self.completed_uploads_count = QtWidgets.QLabel() + self.completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) + + # Toggle button + self.toggle_button = QtWidgets.QPushButton() + self.toggle_button.setDefault(False) + self.toggle_button.setFixedWidth(35) + self.toggle_button.setFixedHeight(30) + self.toggle_button.setFlat(True) + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.toggle_button.clicked.connect(self.toggle_uploads) + + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + + # Layout + layout = QtWidgets.QHBoxLayout() + layout.addStretch() + layout.addWidget(self.in_progress_uploads_count) + layout.addWidget(self.completed_uploads_count) + layout.addWidget(self.toggle_button) + self.setLayout(layout) + + self.update_uploads_completed() + self.update_uploads_in_progress() + + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Uploads is hidden. + """ + if increment and not self.receive_mode.uploads.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.show() + + def update_uploads_completed(self): + """ + Update the 'Uploads completed' info widget. + """ + if self.receive_mode.uploads_completed == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_completed)) + self.completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.receive_mode.uploads_completed)) + + def update_uploads_in_progress(self): + """ + Update the 'Uploads in progress' info widget. + """ + if self.receive_mode.uploads_in_progress == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_in_progress)) + self.in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.receive_mode.uploads_in_progress)) + + def toggle_uploads(self): + """ + Toggle showing and hiding the Uploads widget + """ + self.common.log('ReceiveModeInfo', 'toggle_uploads') + + if self.receive_mode.uploads.isVisible(): + self.receive_mode.uploads.hide() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) + self.toggle_button.setFlat(True) + else: + self.receive_mode.uploads.show() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) + self.toggle_button.setFlat(False) + + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + + self.receive_mode.resize_window() + + def show_less(self): + """ + Remove clutter widgets that aren't necessary. + """ + pass + + def show_more(self): + """ + Show all widgets. + """ + pass diff --git a/onionshare_gui/mode/receive_mode/uploads.py b/onionshare_gui/mode/receive_mode/uploads.py new file mode 100644 index 00000000..c445be47 --- /dev/null +++ b/onionshare_gui/mode/receive_mode/uploads.py @@ -0,0 +1,395 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +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', True).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', True).format( + self.started.strftime("%b %d, %I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%b %d, %I:%M%p") + ) + self.label.setText(text) + + +class UploadList(QtWidgets.QScrollArea): + """ + List of upload progess bars. + """ + def __init__(self, common): + super(UploadList, self).__init__() + self.common = common + + self.uploads = {} + + # The layout that holds all of the uploads + self.uploads_layout = QtWidgets.QVBoxLayout() + self.uploads_layout.setContentsMargins(0, 0, 0, 0) + self.uploads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.uploads_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area + widget = QtWidgets.QWidget() + widget.setLayout(wrapper_layout) + self.setWidget(widget) + self.setWidgetResizable(True) + + # Other scroll area settings + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) + + def add(self, upload_id, content_length): + """ + Add a new upload progress bar. + """ + 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 + """ + for upload in self.uploads.values(): + self.uploads_layout.removeWidget(upload) + upload.progress_bar.close() + self.uploads = {} + + +class Uploads(QtWidgets.QWidget): + """ + 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.setMinimumWidth(350) + + # When there are no uploads + empty_image = QtWidgets.QLabel() + empty_image.setAlignment(QtCore.Qt.AlignCenter) + empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png')))) + empty_text = QtWidgets.QLabel(strings._('gui_no_uploads', True)) + empty_text.setAlignment(QtCore.Qt.AlignCenter) + empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(empty_image) + empty_layout.addWidget(empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setLayout(empty_layout) + + # When there are uploads + self.upload_list = UploadList(self.common) + + # Upload header + uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) + uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + upload_header = QtWidgets.QHBoxLayout() + upload_header.addWidget(uploads_label) + upload_header.addStretch() + upload_header.addWidget(clear_button) + + # Upload layout + not_empty_layout = QtWidgets.QVBoxLayout() + not_empty_layout.addLayout(upload_header) + not_empty_layout.addWidget(self.upload_list) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(not_empty_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) + self.setLayout(layout) + + # Reset once at the beginning + self.reset() + + 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 empty, show not empty + self.empty.hide() + self.not_empty.show() + + # Add it to the list + self.upload_list.add(upload_id, content_length) + + def update(self, upload_id, progress): + """ + Update the progress of an upload. + """ + self.upload_list.update(upload_id, progress) + + def rename(self, upload_id, old_filename, new_filename): + """ + Rename a file, which happens if the filename already exists in downloads_dir. + """ + self.upload_list.rename(upload_id, old_filename, new_filename) + + def finished(self, upload_id): + """ + An upload has finished. + """ + self.upload_list.finished(upload_id) + + 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.upload_list.cancel(upload_id) + + def reset(self): + """ + Reset the uploads back to zero + """ + self.upload_list.reset() + + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py new file mode 100644 index 00000000..c301037b --- /dev/null +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -0,0 +1,378 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import os +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.onion import * +from onionshare.common import Common +from onionshare.web import Web + +from .file_selection import FileSelection +from .downloads import Downloads +from .threads import CompressThread +from .info import ShareModeInfo +from .. import Mode +from ...widgets import Alert + +class ShareMode(Mode): + """ + Parts of the main window UI for sharing files. + """ + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Threads start out as None + self.compress_thread = None + + # Create the Web object + self.web = Web(self.common, True, 'share') + + # File selection + self.file_selection = FileSelection(self.common) + if self.filenames: + for filename in self.filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status.set_mode('share', self.file_selection) + self.server_status.server_started.connect(self.file_selection.server_started) + self.server_status.server_stopped.connect(self.file_selection.server_stopped) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.hide() + + # Downloads + self.downloads = Downloads(self.common) + self.downloads.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + + # Information about share, and show downloads button + self.info = ShareModeInfo(self.common, self) + + # Primary action layout + self.primary_action_layout.addWidget(self.filesize_warning) + self.primary_action.hide() + self.update_primary_action() + + # Status bar, zip progress bar + self._zip_progress_bar = None + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addWidget(self.info) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.downloads) + self.setLayout(self.wrapper_layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def get_stop_server_shutdown_timeout_text(self): + """ + 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) + + def timeout_finished_should_stop_server(self): + """ + The shutdown timer expired, should we stop the server? Returns a bool + """ + # If there were no attempts to download the share, or all downloads are done, we can stop + if self.web.share_mode.download_count == 0 or self.web.done: + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_timeout', True)) + return True + # A download is probably still running - hold off on stopping the share + else: + self.server_status_label.setText(strings._('timeout_download_still_running', True)) + return False + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.share_mode.download_count = 0 + self.web.error404_count = 0 + + # Hide and reset the downloads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + # Add progress bar to the status bar, indicating the compressing of files. + self._zip_progress_bar = ZipProgressBar(self.common, 0) + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self.status_bar.insertWidget(0, self._zip_progress_bar) + + # prepare the files for sending in a new thread + self.compress_thread = CompressThread(self) + self.compress_thread.success.connect(self.starting_server_step3.emit) + self.compress_thread.success.connect(self.start_server_finished.emit) + self.compress_thread.error.connect(self.starting_server_error.emit) + self.server_status.server_canceled.connect(self.compress_thread.cancel) + self.compress_thread.start() + + def start_server_step3_custom(self): + """ + Step 3 in starting the server. Remove zip progess bar, and display large filesize + warning, if applicable. + """ + # Remove zip progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + # Warn about sending large files over Tor + if self.web.share_mode.download_filesize >= 157286400: # 150mb + self.filesize_warning.setText(strings._("large_filesize", True)) + self.filesize_warning.show() + + def start_server_error_custom(self): + """ + Start server error. + """ + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + def stop_server_custom(self): + """ + Stop server. + """ + # Remove the progress bar + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + self.filesize_warning.hide() + self.downloads_in_progress = 0 + self.downloads_completed = 0 + self.info.update_downloads_in_progress() + self.file_selection.file_list.adjustSize() + + def cancel_server_custom(self): + """ + Stop the compression thread on cancel + """ + if self.compress_thread: + self.common.log('ShareMode', 'cancel_server: quitting compress thread') + self.compress_thread.quit() + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + self.info.show_less() + + def handle_request_load(self, event): + """ + Handle REQUEST_LOAD event. + """ + self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) + + def handle_request_started(self, event): + """ + Handle REQUEST_STARTED event. + """ + if event["data"]["use_gzip"]: + filesize = self.web.share_mode.gzip_filesize + else: + filesize = self.web.share_mode.download_filesize + self.downloads.add(event["data"]["id"], filesize) + self.info.update_indicator(True) + self.downloads_in_progress += 1 + self.info.update_downloads_in_progress() + + self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) + + def handle_request_progress(self, event): + """ + Handle REQUEST_PROGRESS event. + """ + self.downloads.update(event["data"]["id"], event["data"]["bytes"]) + + # Is the download complete? + 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)) + + # Update the total 'completed downloads' info + self.downloads_completed += 1 + self.info.update_downloads_completed() + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.info.update_downloads_in_progress() + + # Close on finish? + if self.common.settings.get('close_after_first_download'): + self.server_status.stop_server() + self.status_bar.clearMessage() + self.server_status_label.setText(strings._('closing_automatically', True)) + else: + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.downloads.cancel(event["data"]["id"]) + self.downloads_in_progress = 0 + self.info.update_downloads_in_progress() + + def handle_request_canceled(self, event): + """ + Handle REQUEST_CANCELED event. + """ + self.downloads.cancel(event["data"]["id"]) + + # Update the 'in progress downloads' info + self.downloads_in_progress -= 1 + self.info.update_downloads_in_progress() + self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + + def on_reload_settings(self): + """ + If there were some files listed for sharing, we should be ok to re-enable + the 'Start Sharing' button now. + """ + if self.server_status.file_selection.get_num_files() > 0: + self.primary_action.show() + self.info.show_more() + + def update_primary_action(self): + self.common.log('ShareMode', 'update_primary_action') + + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info.show_more() + + # Update the file count in the info label + total_size_bytes = 0 + for index in range(self.file_selection.file_list.count()): + item = self.file_selection.file_list.item(index) + total_size_bytes += item.size_bytes + total_size_readable = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info.update_label(strings._('gui_file_info', True).format(file_count, total_size_readable)) + else: + self.info.update_label(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + + else: + self.primary_action.hide() + self.info.show_less() + + # Resize window + self.resize_window() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.downloads_completed = 0 + self.downloads_in_progress = 0 + self.info.update_downloads_completed() + self.info.update_downloads_in_progress() + self.downloads.reset() + + def resize_window(self): + min_width = self.common.min_window_width + if self.downloads.isVisible(): + min_width += 300 + self.adjust_size.emit(min_width) + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size + + +class ZipProgressBar(QtWidgets.QProgressBar): + update_processed_size_signal = QtCore.pyqtSignal(int) + + def __init__(self, common, total_files_size): + super(ZipProgressBar, self).__init__() + self.common = common + + self.setMaximumHeight(20) + self.setMinimumWidth(200) + self.setValue(0) + self.setFormat(strings._('zip_progress_bar_format')) + self.setStyleSheet(self.common.css['share_zip_progess_bar']) + + self._total_files_size = total_files_size + self._processed_size = 0 + + self.update_processed_size_signal.connect(self.update_processed_size) + + @property + def total_files_size(self): + return self._total_files_size + + @total_files_size.setter + def total_files_size(self, val): + self._total_files_size = val + + @property + def processed_size(self): + return self._processed_size + + @processed_size.setter + def processed_size(self, val): + self.update_processed_size(val) + + def update_processed_size(self, val): + self._processed_size = val + + if self.processed_size < self.total_files_size: + self.setValue(int((self.processed_size * 100) / self.total_files_size)) + elif self.total_files_size != 0: + self.setValue(100) + else: + self.setValue(0) diff --git a/onionshare_gui/mode/share_mode/downloads.py b/onionshare_gui/mode/share_mode/downloads.py new file mode 100644 index 00000000..e78231ad --- /dev/null +++ b/onionshare_gui/mode/share_mode/downloads.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import time +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class Download(QtWidgets.QWidget): + def __init__(self, common, download_id, total_bytes): + super(Download, self).__init__() + self.common = common + + self.download_id = download_id + self.started = time.time() + self.total_bytes = total_bytes + self.downloaded_bytes = 0 + + self.setStyleSheet('QWidget { border: 1px solid red; }') + + # 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 + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.progress_bar) + self.setLayout(layout) + + # 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 DownloadList(QtWidgets.QScrollArea): + """ + List of download progress bars. + """ + def __init__(self, common): + super(DownloadList, self).__init__() + self.common = common + + self.downloads = {} + + # The layout that holds all of the downloads + self.downloads_layout = QtWidgets.QVBoxLayout() + self.downloads_layout.setContentsMargins(0, 0, 0, 0) + self.downloads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.downloads_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area + widget = QtWidgets.QWidget() + widget.setLayout(wrapper_layout) + self.setWidget(widget) + self.setWidgetResizable(True) + + # Other scroll area settings + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) + + def add(self, download_id, content_length): + """ + Add a new download progress bar. + """ + download = Download(self.common, download_id, content_length) + self.downloads[download_id] = download + self.downloads_layout.addWidget(download) + + 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) + download.progress_bar.close() + self.downloads = {} + + +class Downloads(QtWidgets.QWidget): + """ + 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.setMinimumWidth(350) + + # When there are no downloads + empty_image = QtWidgets.QLabel() + empty_image.setAlignment(QtCore.Qt.AlignCenter) + empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png')))) + empty_text = QtWidgets.QLabel(strings._('gui_no_downloads', True)) + empty_text.setAlignment(QtCore.Qt.AlignCenter) + empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(empty_image) + empty_layout.addWidget(empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setLayout(empty_layout) + + # When there are downloads + self.download_list = DownloadList(self.common) + + # Download header + downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) + downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + download_header = QtWidgets.QHBoxLayout() + download_header.addWidget(downloads_label) + download_header.addStretch() + download_header.addWidget(clear_button) + + # Download layout + not_empty_layout = QtWidgets.QVBoxLayout() + not_empty_layout.addLayout(download_header) + not_empty_layout.addWidget(self.download_list) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(not_empty_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) + self.setLayout(layout) + + # Reset once at the beginning + self.reset() + + def add(self, download_id, content_length): + """ + Add a new download progress bar. + """ + self.common.log('Downloads', 'add', 'download_id: {}, content_length: {}'.format(download_id, content_length)) + + # Hide empty, show not empty + self.empty.hide() + self.not_empty.show() + + # Add it to the list + self.download_list.add(download_id, content_length) + + def update(self, download_id, downloaded_bytes): + """ + Update the progress of a download progress bar. + """ + self.download_list.update(download_id, downloaded_bytes) + + def cancel(self, download_id): + """ + Update a download progress bar to show that it has been canceled. + """ + self.download_list.cancel(download_id) + + def reset(self): + """ + Reset the downloads back to zero + """ + self.download_list.reset() + + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py new file mode 100644 index 00000000..d59df234 --- /dev/null +++ b/onionshare_gui/mode/share_mode/file_selection.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import os +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + +from ...widgets import Alert, AddFileDialog + +class DropHereLabel(QtWidgets.QLabel): + """ + When there are no files or folders in the FileList yet, display the + 'drop files here' message and graphic. + """ + def __init__(self, common, parent, image=False): + self.parent = parent + super(DropHereLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + + if image: + self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + else: + self.setText(strings._('gui_drag_and_drop', True)) + self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + + self.hide() + + def dragEnterEvent(self, event): + self.parent.drop_here_image.hide() + self.parent.drop_here_text.hide() + event.accept() + + +class DropCountLabel(QtWidgets.QLabel): + """ + While dragging files over the FileList, this counter displays the + number of files you're dragging. + """ + def __init__(self, common, parent): + self.parent = parent + super(DropCountLabel, self).__init__(parent=parent) + + self.common = common + + self.setAcceptDrops(True) + self.setAlignment(QtCore.Qt.AlignCenter) + self.setText(strings._('gui_drag_and_drop', True)) + self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.hide() + + def dragEnterEvent(self, event): + self.hide() + event.accept() + + +class FileList(QtWidgets.QListWidget): + """ + The list of files and folders in the GUI. + """ + files_dropped = QtCore.pyqtSignal() + files_updated = QtCore.pyqtSignal() + + def __init__(self, common, parent=None): + super(FileList, self).__init__(parent) + + self.common = common + + self.setAcceptDrops(True) + self.setIconSize(QtCore.QSize(32, 32)) + self.setSortingEnabled(True) + self.setMinimumHeight(205) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.drop_here_image = DropHereLabel(self.common, self, True) + self.drop_here_text = DropHereLabel(self.common, self, False) + self.drop_count = DropCountLabel(self.common, self) + self.resizeEvent(None) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # file list should have a background image if empty + if self.count() == 0: + self.drop_here_image.show() + self.drop_here_text.show() + else: + self.drop_here_image.hide() + self.drop_here_text.hide() + + def server_started(self): + """ + Update the GUI when the server starts, by hiding delete buttons. + """ + self.setAcceptDrops(False) + self.setCurrentItem(None) + self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + for index in range(self.count()): + self.item(index).item_button.hide() + + def server_stopped(self): + """ + Update the GUI when the server stops, by showing delete buttons. + """ + self.setAcceptDrops(True) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + for index in range(self.count()): + self.item(index).item_button.show() + + def resizeEvent(self, event): + """ + When the widget is resized, resize the drop files image and text. + """ + offset = 70 + self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) + self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) + + if self.count() > 0: + # Add and delete an empty item, to force all items to get redrawn + # This is ugly, but the only way I could figure out how to proceed + item = QtWidgets.QListWidgetItem('fake item') + self.addItem(item) + self.takeItem(self.row(item)) + self.update() + + # Extend any filenames that were truncated to fit the window + # We use 200 as a rough guess at how wide the 'file size + delete button' widget is + # and extend based on the overall width minus that amount. + for index in range(self.count()): + metrics = QtGui.QFontMetrics(self.item(index).font()) + elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + self.item(index).setText(elided) + + + def dragEnterEvent(self, event): + """ + dragEnterEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + count = len(event.mimeData().urls()) + self.drop_count.setText('+{}'.format(count)) + + size_hint = self.drop_count.sizeHint() + self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.show() + event.accept() + else: + event.ignore() + + def dragLeaveEvent(self, event): + """ + dragLeaveEvent for dragging files and directories into the widget. + """ + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.drop_count.hide() + event.accept() + self.update() + + def dragMoveEvent(self, event): + """ + dragMoveEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dropEvent(self, event): + """ + dropEvent for dragging files and directories into the widget. + """ + if event.mimeData().hasUrls: + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + for url in event.mimeData().urls(): + filename = str(url.toLocalFile()) + self.add_file(filename) + else: + event.ignore() + + self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.drop_count.hide() + + self.files_dropped.emit() + + def add_file(self, filename): + """ + Add a file or directory to this widget. + """ + filenames = [] + for index in range(self.count()): + filenames.append(self.item(index).filename) + + if filename not in filenames: + if not os.access(filename, os.R_OK): + Alert(self.common, strings._("not_a_readable_file", True).format(filename)) + return + + fileinfo = QtCore.QFileInfo(filename) + ip = QtWidgets.QFileIconProvider() + icon = ip.icon(fileinfo) + + if os.path.isfile(filename): + size_bytes = fileinfo.size() + size_readable = self.common.human_readable_filesize(size_bytes) + else: + size_bytes = self.common.dir_size(filename) + size_readable = self.common.human_readable_filesize(size_bytes) + + # Create a new item + item = QtWidgets.QListWidgetItem() + item.setIcon(icon) + item.size_bytes = size_bytes + + # Item's filename attribute and size labels + item.filename = filename + item_size = QtWidgets.QLabel(size_readable) + item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + + item.basename = os.path.basename(filename.rstrip('/')) + # Use the basename as the method with which to sort the list + metrics = QtGui.QFontMetrics(item.font()) + elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + item.setData(QtCore.Qt.DisplayRole, elided) + + # Item's delete button + def delete_item(): + itemrow = self.row(item) + self.takeItem(itemrow) + self.files_updated.emit() + + item.item_button = QtWidgets.QPushButton() + item.item_button.setDefault(False) + item.item_button.setFlat(True) + item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.clicked.connect(delete_item) + item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + + # Item info widget, with a white background + item_info_layout = QtWidgets.QHBoxLayout() + item_info_layout.addWidget(item_size) + item_info_layout.addWidget(item.item_button) + item_info = QtWidgets.QWidget() + item_info.setObjectName('item-info') + item_info.setLayout(item_info_layout) + + # Create the item's widget and layouts + item_hlayout = QtWidgets.QHBoxLayout() + item_hlayout.addStretch() + item_hlayout.addWidget(item_info) + widget = QtWidgets.QWidget() + widget.setLayout(item_hlayout) + + item.setSizeHint(widget.sizeHint()) + + self.addItem(item) + self.setItemWidget(item, widget) + + self.files_updated.emit() + + +class FileSelection(QtWidgets.QVBoxLayout): + """ + The list of files and folders in the GUI, as well as buttons to add and + delete the files and folders. + """ + def __init__(self, common): + super(FileSelection, self).__init__() + + self.common = common + + self.server_on = False + + # File list + self.file_list = FileList(self.common) + self.file_list.itemSelectionChanged.connect(self.update) + self.file_list.files_dropped.connect(self.update) + self.file_list.files_updated.connect(self.update) + + # Buttons + self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) + self.add_button.clicked.connect(self.add) + self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) + self.delete_button.clicked.connect(self.delete) + button_layout = QtWidgets.QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(self.add_button) + button_layout.addWidget(self.delete_button) + + # Add the widgets + self.addWidget(self.file_list) + self.addLayout(button_layout) + + self.update() + + def update(self): + """ + Update the GUI elements based on the current state. + """ + # All buttons should be hidden if the server is on + if self.server_on: + self.add_button.hide() + self.delete_button.hide() + else: + self.add_button.show() + + # Delete button should be hidden if item isn't selected + if len(self.file_list.selectedItems()) == 0: + self.delete_button.hide() + else: + self.delete_button.show() + + # Update the file list + self.file_list.update() + + def add(self): + """ + Add button clicked. + """ + file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items', True)) + if file_dialog.exec_() == QtWidgets.QDialog.Accepted: + for filename in file_dialog.selectedFiles(): + self.file_list.add_file(filename) + + self.file_list.setCurrentItem(None) + self.update() + + def delete(self): + """ + Delete button clicked + """ + selected = self.file_list.selectedItems() + for item in selected: + itemrow = self.file_list.row(item) + self.file_list.takeItem(itemrow) + self.file_list.files_updated.emit() + + self.file_list.setCurrentItem(None) + self.update() + + def server_started(self): + """ + Gets called when the server starts. + """ + self.server_on = True + self.file_list.server_started() + self.update() + + def server_stopped(self): + """ + Gets called when the server stops. + """ + self.server_on = False + self.file_list.server_stopped() + self.update() + + def get_num_files(self): + """ + Returns the total number of files and folders in the list. + """ + return len(range(self.file_list.count())) + + def setFocus(self): + """ + Set the Qt app focus on the file selection box. + """ + self.file_list.setFocus() diff --git a/onionshare_gui/mode/share_mode/info.py b/onionshare_gui/mode/share_mode/info.py new file mode 100644 index 00000000..c692649c --- /dev/null +++ b/onionshare_gui/mode/share_mode/info.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class ShareModeInfo(QtWidgets.QWidget): + """ + Share mode information widget + """ + def __init__(self, common, share_mode): + super(ShareModeInfo, self).__init__() + self.common = common + self.share_mode = share_mode + + # Label + self.label_text = "" + self.label = QtWidgets.QLabel() + self.label.setStyleSheet(self.common.css['mode_info_label']) + + # In progress and completed labels + self.in_progress_downloads_count = QtWidgets.QLabel() + self.in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) + self.completed_downloads_count = QtWidgets.QLabel() + self.completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) + + # Toggle button + self.toggle_button = QtWidgets.QPushButton() + self.toggle_button.setDefault(False) + self.toggle_button.setFixedWidth(35) + self.toggle_button.setFixedHeight(30) + self.toggle_button.setFlat(True) + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.toggle_button.clicked.connect(self.toggle_downloads) + + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + + # Layout + layout = QtWidgets.QHBoxLayout() + layout.addWidget(self.label) + layout.addStretch() + layout.addWidget(self.in_progress_downloads_count) + layout.addWidget(self.completed_downloads_count) + layout.addWidget(self.toggle_button) + self.setLayout(layout) + + self.update_downloads_completed() + self.update_downloads_in_progress() + + def update_label(self, s): + """ + Updates the text of the label. + """ + self.label_text = s + self.label.setText(self.label_text) + + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Downloads is hidden. + """ + if increment and not self.share_mode.downloads.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.show() + + def update_downloads_completed(self): + """ + Update the 'Downloads completed' info widget. + """ + if self.share_mode.downloads_completed == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_completed)) + self.completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.share_mode.downloads_completed)) + + def update_downloads_in_progress(self): + """ + Update the 'Downloads in progress' info widget. + """ + if self.share_mode.downloads_in_progress == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_in_progress)) + self.in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.share_mode.downloads_in_progress)) + + def toggle_downloads(self): + """ + Toggle showing and hiding the Downloads widget + """ + self.common.log('ShareModeInfo', 'toggle_downloads') + + if self.share_mode.downloads.isVisible(): + self.share_mode.downloads.hide() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) + self.toggle_button.setFlat(True) + else: + self.share_mode.downloads.show() + self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) + self.toggle_button.setFlat(False) + + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + + self.share_mode.resize_window() + + def show_less(self): + """ + Remove clutter widgets that aren't necessary. + """ + self.label.setText("") + + def show_more(self): + """ + Show all widgets. + """ + self.label.setText(self.label_text) diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py new file mode 100644 index 00000000..d6022746 --- /dev/null +++ b/onionshare_gui/mode/share_mode/threads.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore + + +class CompressThread(QtCore.QThread): + """ + Compresses files to be shared + """ + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def __init__(self, mode): + super(CompressThread, self).__init__() + self.mode = mode + self.mode.common.log('CompressThread', '__init__') + + # prepare files to share + def set_processed_size(self, x): + if self.mode._zip_progress_bar != None: + self.mode._zip_progress_bar.update_processed_size_signal.emit(x) + + def run(self): + self.mode.common.log('CompressThread', 'run') + + try: + if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): + self.success.emit() + else: + # Cancelled + pass + + self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames + except OSError as e: + self.error.emit(e.strerror) + + def cancel(self): + self.mode.common.log('CompressThread', 'cancel') + + # Let the Web and ZipWriter objects know that we're canceling compression early + self.mode.web.cancel_compression = True + if self.mode.web.zip_writer: + self.mode.web.zip_writer.cancel_compression = True diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index ced53ede..6a7eb63a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -23,8 +23,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.web import Web -from .share_mode import ShareMode -from .receive_mode import ReceiveMode +from .mode.share_mode import ShareMode +from .mode.receive_mode import ReceiveMode from .tor_connection_dialog import TorConnectionDialog from .settings_dialog import SettingsDialog diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py deleted file mode 100644 index 6430382b..00000000 --- a/onionshare_gui/receive_mode/__init__.py +++ /dev/null @@ -1,200 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from onionshare.web import Web - -from .uploads import Uploads -from .info import ReceiveModeInfo -from ..mode import Mode - -class ReceiveMode(Mode): - """ - Parts of the main window UI for receiving files. - """ - def init(self): - """ - Custom initialization for ReceiveMode. - """ - # Create the Web object - self.web = Web(self.common, True, 'receive') - - # Server status - self.server_status.set_mode('receive') - self.server_status.server_started_finished.connect(self.update_primary_action) - self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.update_primary_action) - - # Tell server_status about web, then update - self.server_status.web = self.web - self.server_status.update() - - # Uploads - self.uploads = Uploads(self.common) - self.uploads.hide() - self.uploads_in_progress = 0 - self.uploads_completed = 0 - self.new_upload = False # For scrolling to the bottom of the uploads list - - # Information about share, and show uploads button - self.info = ReceiveModeInfo(self.common, self) - self.info.show_less() - - # Receive mode info - self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) - self.receive_info.setMinimumHeight(80) - self.receive_info.setWordWrap(True) - - # Main layout - self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info) - self.main_layout.addWidget(self.receive_info) - self.main_layout.addWidget(self.primary_action) - self.main_layout.addStretch() - self.main_layout.addWidget(self.min_width_widget) - - # Wrapper layout - self.wrapper_layout = QtWidgets.QHBoxLayout() - self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.uploads) - self.setLayout(self.wrapper_layout) - - def get_stop_server_shutdown_timeout_text(self): - """ - 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) - - def timeout_finished_should_stop_server(self): - """ - The shutdown timer expired, should we stop the server? Returns a bool - """ - # TODO: wait until the final upload is done before stoppign the server? - return True - - def start_server_custom(self): - """ - Starting the server. - """ - # Reset web counters - self.web.receive_mode.upload_count = 0 - self.web.error404_count = 0 - - # Hide and reset the uploads if we have previously shared - self.reset_info_counters() - - def start_server_step2_custom(self): - """ - Step 2 in starting the server. - """ - # Continue - self.starting_server_step3.emit() - self.start_server_finished.emit() - - def handle_tor_broke_custom(self): - """ - Connection to Tor broke. - """ - self.primary_action.hide() - self.info.show_less() - - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_upload_page_loaded_message', True)) - - def handle_request_started(self, event): - """ - Handle REQUEST_STARTED event. - """ - self.uploads.add(event["data"]["id"], event["data"]["content_length"]) - self.info.update_indicator(True) - self.uploads_in_progress += 1 - self.info.update_uploads_in_progress() - - self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - self.uploads.update(event["data"]["id"], event["data"]["progress"]) - - def handle_request_close_server(self, event): - """ - Handle REQUEST_CLOSE_SERVER event. - """ - self.stop_server() - self.system_tray.showMessage(strings._('systray_close_server_title', True), strings._('systray_close_server_message', True)) - - def handle_request_upload_file_renamed(self, event): - """ - Handle REQUEST_UPLOAD_FILE_RENAMED event. - """ - self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"]) - - def handle_request_upload_finished(self, event): - """ - Handle REQUEST_UPLOAD_FINISHED event. - """ - self.uploads.finished(event["data"]["id"]) - # Update the total 'completed uploads' info - self.uploads_completed += 1 - self.info.update_uploads_completed() - # Update the 'in progress uploads' info - self.uploads_in_progress -= 1 - self.info.update_uploads_in_progress() - - def on_reload_settings(self): - """ - We should be ok to re-enable the 'Start Receive Mode' button now. - """ - self.primary_action.show() - self.info.show_more() - - def reset_info_counters(self): - """ - Set the info counters back to zero. - """ - self.uploads_completed = 0 - self.uploads_in_progress = 0 - self.info.update_uploads_completed() - self.info.update_uploads_in_progress() - self.uploads.reset() - - def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') - - # Show the info widget when the server is active - if self.server_status.status == self.server_status.STATUS_STARTED: - self.info.show_more() - else: - self.info.show_less() - - # Resize window - self.resize_window() - - def resize_window(self): - min_width = self.common.min_window_width - if self.uploads.isVisible(): - min_width += 300 - self.adjust_size.emit(min_width) diff --git a/onionshare_gui/receive_mode/info.py b/onionshare_gui/receive_mode/info.py deleted file mode 100644 index c23f8496..00000000 --- a/onionshare_gui/receive_mode/info.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class ReceiveModeInfo(QtWidgets.QWidget): - """ - Receive mode information widget - """ - def __init__(self, common, receive_mode): - super(ReceiveModeInfo, self).__init__() - self.common = common - self.receive_mode = receive_mode - - # In progress and completed labels - self.in_progress_uploads_count = QtWidgets.QLabel() - self.in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - self.completed_uploads_count = QtWidgets.QLabel() - self.completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - # Toggle button - self.toggle_button = QtWidgets.QPushButton() - self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(35) - self.toggle_button.setFixedHeight(30) - self.toggle_button.setFlat(True) - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.toggle_button.clicked.connect(self.toggle_uploads) - - # Keep track of indicator - self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) - self.update_indicator() - - # Layout - layout = QtWidgets.QHBoxLayout() - layout.addStretch() - layout.addWidget(self.in_progress_uploads_count) - layout.addWidget(self.completed_uploads_count) - layout.addWidget(self.toggle_button) - self.setLayout(layout) - - self.update_uploads_completed() - self.update_uploads_in_progress() - - def update_indicator(self, increment=False): - """ - Update the display of the indicator count. If increment is True, then - only increment the counter if Uploads is hidden. - """ - if increment and not self.receive_mode.uploads.isVisible(): - self.indicator_count += 1 - - self.indicator_label.setText("{}".format(self.indicator_count)) - - if self.indicator_count == 0: - self.indicator_label.hide() - else: - size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) - self.indicator_label.show() - - def update_uploads_completed(self): - """ - Update the 'Uploads completed' info widget. - """ - if self.receive_mode.uploads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_completed)) - self.completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.receive_mode.uploads_completed)) - - def update_uploads_in_progress(self): - """ - Update the 'Uploads in progress' info widget. - """ - if self.receive_mode.uploads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_in_progress)) - self.in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.receive_mode.uploads_in_progress)) - - def toggle_uploads(self): - """ - Toggle showing and hiding the Uploads widget - """ - self.common.log('ReceiveModeInfo', 'toggle_uploads') - - if self.receive_mode.uploads.isVisible(): - self.receive_mode.uploads.hide() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.toggle_button.setFlat(True) - else: - self.receive_mode.uploads.show() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) - self.toggle_button.setFlat(False) - - # Reset the indicator count - self.indicator_count = 0 - self.update_indicator() - - self.receive_mode.resize_window() - - def show_less(self): - """ - Remove clutter widgets that aren't necessary. - """ - pass - - def show_more(self): - """ - Show all widgets. - """ - pass diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py deleted file mode 100644 index f08b35cc..00000000 --- a/onionshare_gui/receive_mode/uploads.py +++ /dev/null @@ -1,395 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -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', True).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', True).format( - self.started.strftime("%b %d, %I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range', True).format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range', True).format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%b %d, %I:%M%p") - ) - self.label.setText(text) - - -class UploadList(QtWidgets.QScrollArea): - """ - List of upload progess bars. - """ - def __init__(self, common): - super(UploadList, self).__init__() - self.common = common - - self.uploads = {} - - # The layout that holds all of the uploads - self.uploads_layout = QtWidgets.QVBoxLayout() - self.uploads_layout.setContentsMargins(0, 0, 0, 0) - self.uploads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - - # Wrapper layout that also contains a stretch - wrapper_layout = QtWidgets.QVBoxLayout() - wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - wrapper_layout.addLayout(self.uploads_layout) - wrapper_layout.addStretch() - - # The internal widget of the scroll area - widget = QtWidgets.QWidget() - widget.setLayout(wrapper_layout) - self.setWidget(widget) - self.setWidgetResizable(True) - - # Other scroll area settings - self.setBackgroundRole(QtGui.QPalette.Light) - self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.verticalScrollBar().setValue(maximum) - - def add(self, upload_id, content_length): - """ - Add a new upload progress bar. - """ - 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 - """ - for upload in self.uploads.values(): - self.uploads_layout.removeWidget(upload) - upload.progress_bar.close() - self.uploads = {} - - -class Uploads(QtWidgets.QWidget): - """ - 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.setMinimumWidth(350) - - # When there are no uploads - empty_image = QtWidgets.QLabel() - empty_image.setAlignment(QtCore.Qt.AlignCenter) - empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png')))) - empty_text = QtWidgets.QLabel(strings._('gui_no_uploads', True)) - empty_text.setAlignment(QtCore.Qt.AlignCenter) - empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) - empty_layout = QtWidgets.QVBoxLayout() - empty_layout.addStretch() - empty_layout.addWidget(empty_image) - empty_layout.addWidget(empty_text) - empty_layout.addStretch() - self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) - self.empty.setLayout(empty_layout) - - # When there are uploads - self.upload_list = UploadList(self.common) - - # Upload header - uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) - uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) - upload_header = QtWidgets.QHBoxLayout() - upload_header.addWidget(uploads_label) - upload_header.addStretch() - upload_header.addWidget(clear_button) - - # Upload layout - not_empty_layout = QtWidgets.QVBoxLayout() - not_empty_layout.addLayout(upload_header) - not_empty_layout.addWidget(self.upload_list) - self.not_empty = QtWidgets.QWidget() - self.not_empty.setLayout(not_empty_layout) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.empty) - layout.addWidget(self.not_empty) - self.setLayout(layout) - - # Reset once at the beginning - self.reset() - - 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 empty, show not empty - self.empty.hide() - self.not_empty.show() - - # Add it to the list - self.upload_list.add(upload_id, content_length) - - def update(self, upload_id, progress): - """ - Update the progress of an upload. - """ - self.upload_list.update(upload_id, progress) - - def rename(self, upload_id, old_filename, new_filename): - """ - Rename a file, which happens if the filename already exists in downloads_dir. - """ - self.upload_list.rename(upload_id, old_filename, new_filename) - - def finished(self, upload_id): - """ - An upload has finished. - """ - self.upload_list.finished(upload_id) - - 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.upload_list.cancel(upload_id) - - def reset(self): - """ - Reset the uploads back to zero - """ - self.upload_list.reset() - - # Hide not empty, show empty - self.not_empty.hide() - self.empty.show() diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py deleted file mode 100644 index c44e8beb..00000000 --- a/onionshare_gui/share_mode/__init__.py +++ /dev/null @@ -1,378 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import os -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from onionshare.onion import * -from onionshare.common import Common -from onionshare.web import Web - -from .file_selection import FileSelection -from .downloads import Downloads -from .threads import CompressThread -from .info import ShareModeInfo -from ..mode import Mode -from ..widgets import Alert - -class ShareMode(Mode): - """ - Parts of the main window UI for sharing files. - """ - def init(self): - """ - Custom initialization for ReceiveMode. - """ - # Threads start out as None - self.compress_thread = None - - # Create the Web object - self.web = Web(self.common, True, 'share') - - # File selection - self.file_selection = FileSelection(self.common) - if self.filenames: - for filename in self.filenames: - self.file_selection.file_list.add_file(filename) - - # Server status - self.server_status.set_mode('share', self.file_selection) - self.server_status.server_started.connect(self.file_selection.server_started) - self.server_status.server_stopped.connect(self.file_selection.server_stopped) - self.server_status.server_stopped.connect(self.update_primary_action) - self.server_status.server_canceled.connect(self.file_selection.server_stopped) - self.server_status.server_canceled.connect(self.update_primary_action) - self.file_selection.file_list.files_updated.connect(self.server_status.update) - self.file_selection.file_list.files_updated.connect(self.update_primary_action) - # Tell server_status about web, then update - self.server_status.web = self.web - self.server_status.update() - - # Filesize warning - self.filesize_warning = QtWidgets.QLabel() - self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) - self.filesize_warning.hide() - - # Downloads - self.downloads = Downloads(self.common) - self.downloads.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - - # Information about share, and show downloads button - self.info = ShareModeInfo(self.common, self) - - # Primary action layout - self.primary_action_layout.addWidget(self.filesize_warning) - self.primary_action.hide() - self.update_primary_action() - - # Status bar, zip progress bar - self._zip_progress_bar = None - - # Main layout - self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info) - self.main_layout.addLayout(self.file_selection) - self.main_layout.addWidget(self.primary_action) - self.main_layout.addWidget(self.min_width_widget) - - # Wrapper layout - self.wrapper_layout = QtWidgets.QHBoxLayout() - self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.downloads) - self.setLayout(self.wrapper_layout) - - # Always start with focus on file selection - self.file_selection.setFocus() - - def get_stop_server_shutdown_timeout_text(self): - """ - 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) - - def timeout_finished_should_stop_server(self): - """ - The shutdown timer expired, should we stop the server? Returns a bool - """ - # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.share_mode.download_count == 0 or self.web.done: - self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_timeout', True)) - return True - # A download is probably still running - hold off on stopping the share - else: - self.server_status_label.setText(strings._('timeout_download_still_running', True)) - return False - - def start_server_custom(self): - """ - Starting the server. - """ - # Reset web counters - self.web.share_mode.download_count = 0 - self.web.error404_count = 0 - - # Hide and reset the downloads if we have previously shared - self.reset_info_counters() - - def start_server_step2_custom(self): - """ - Step 2 in starting the server. Zipping up files. - """ - # Add progress bar to the status bar, indicating the compressing of files. - self._zip_progress_bar = ZipProgressBar(self.common, 0) - self.filenames = [] - for index in range(self.file_selection.file_list.count()): - self.filenames.append(self.file_selection.file_list.item(index).filename) - - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) - - # prepare the files for sending in a new thread - self.compress_thread = CompressThread(self) - self.compress_thread.success.connect(self.starting_server_step3.emit) - self.compress_thread.success.connect(self.start_server_finished.emit) - self.compress_thread.error.connect(self.starting_server_error.emit) - self.server_status.server_canceled.connect(self.compress_thread.cancel) - self.compress_thread.start() - - def start_server_step3_custom(self): - """ - Step 3 in starting the server. Remove zip progess bar, and display large filesize - warning, if applicable. - """ - # Remove zip progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - # Warn about sending large files over Tor - if self.web.share_mode.download_filesize >= 157286400: # 150mb - self.filesize_warning.setText(strings._("large_filesize", True)) - self.filesize_warning.show() - - def start_server_error_custom(self): - """ - Start server error. - """ - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - def stop_server_custom(self): - """ - Stop server. - """ - # Remove the progress bar - if self._zip_progress_bar is not None: - self.status_bar.removeWidget(self._zip_progress_bar) - self._zip_progress_bar = None - - self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - self.info.update_downloads_in_progress() - self.file_selection.file_list.adjustSize() - - def cancel_server_custom(self): - """ - Stop the compression thread on cancel - """ - if self.compress_thread: - self.common.log('ShareMode', 'cancel_server: quitting compress thread') - self.compress_thread.quit() - - def handle_tor_broke_custom(self): - """ - Connection to Tor broke. - """ - self.primary_action.hide() - self.info.show_less() - - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title', True), strings._('systray_download_page_loaded_message', True)) - - def handle_request_started(self, event): - """ - Handle REQUEST_STARTED event. - """ - if event["data"]["use_gzip"]: - filesize = self.web.share_mode.gzip_filesize - else: - filesize = self.web.share_mode.download_filesize - self.downloads.add(event["data"]["id"], filesize) - self.info.update_indicator(True) - self.downloads_in_progress += 1 - self.info.update_downloads_in_progress() - - self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) - - def handle_request_progress(self, event): - """ - Handle REQUEST_PROGRESS event. - """ - self.downloads.update(event["data"]["id"], event["data"]["bytes"]) - - # Is the download complete? - 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)) - - # Update the total 'completed downloads' info - self.downloads_completed += 1 - self.info.update_downloads_completed() - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.info.update_downloads_in_progress() - - # Close on finish? - if self.common.settings.get('close_after_first_download'): - self.server_status.stop_server() - self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically', True)) - else: - if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel(event["data"]["id"]) - self.downloads_in_progress = 0 - self.info.update_downloads_in_progress() - - def handle_request_canceled(self, event): - """ - Handle REQUEST_CANCELED event. - """ - self.downloads.cancel(event["data"]["id"]) - - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - self.info.update_downloads_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) - - def on_reload_settings(self): - """ - If there were some files listed for sharing, we should be ok to re-enable - the 'Start Sharing' button now. - """ - if self.server_status.file_selection.get_num_files() > 0: - self.primary_action.show() - self.info.show_more() - - def update_primary_action(self): - self.common.log('ShareMode', 'update_primary_action') - - # Show or hide primary action layout - file_count = self.file_selection.file_list.count() - if file_count > 0: - self.primary_action.show() - self.info.show_more() - - # Update the file count in the info label - total_size_bytes = 0 - for index in range(self.file_selection.file_list.count()): - item = self.file_selection.file_list.item(index) - total_size_bytes += item.size_bytes - total_size_readable = self.common.human_readable_filesize(total_size_bytes) - - if file_count > 1: - self.info.update_label(strings._('gui_file_info', True).format(file_count, total_size_readable)) - else: - self.info.update_label(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) - - else: - self.primary_action.hide() - self.info.show_less() - - # Resize window - self.resize_window() - - def reset_info_counters(self): - """ - Set the info counters back to zero. - """ - self.downloads_completed = 0 - self.downloads_in_progress = 0 - self.info.update_downloads_completed() - self.info.update_downloads_in_progress() - self.downloads.reset() - - def resize_window(self): - min_width = self.common.min_window_width - if self.downloads.isVisible(): - min_width += 300 - self.adjust_size.emit(min_width) - - @staticmethod - def _compute_total_size(filenames): - total_size = 0 - for filename in filenames: - if os.path.isfile(filename): - total_size += os.path.getsize(filename) - if os.path.isdir(filename): - total_size += Common.dir_size(filename) - return total_size - - -class ZipProgressBar(QtWidgets.QProgressBar): - update_processed_size_signal = QtCore.pyqtSignal(int) - - def __init__(self, common, total_files_size): - super(ZipProgressBar, self).__init__() - self.common = common - - self.setMaximumHeight(20) - self.setMinimumWidth(200) - self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - self.setStyleSheet(self.common.css['share_zip_progess_bar']) - - self._total_files_size = total_files_size - self._processed_size = 0 - - self.update_processed_size_signal.connect(self.update_processed_size) - - @property - def total_files_size(self): - return self._total_files_size - - @total_files_size.setter - def total_files_size(self, val): - self._total_files_size = val - - @property - def processed_size(self): - return self._processed_size - - @processed_size.setter - def processed_size(self, val): - self.update_processed_size(val) - - def update_processed_size(self, val): - self._processed_size = val - - if self.processed_size < self.total_files_size: - self.setValue(int((self.processed_size * 100) / self.total_files_size)) - elif self.total_files_size != 0: - self.setValue(100) - else: - self.setValue(0) diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py deleted file mode 100644 index e78231ad..00000000 --- a/onionshare_gui/share_mode/downloads.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import time -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class Download(QtWidgets.QWidget): - def __init__(self, common, download_id, total_bytes): - super(Download, self).__init__() - self.common = common - - self.download_id = download_id - self.started = time.time() - self.total_bytes = total_bytes - self.downloaded_bytes = 0 - - self.setStyleSheet('QWidget { border: 1px solid red; }') - - # 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 - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.progress_bar) - self.setLayout(layout) - - # 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 DownloadList(QtWidgets.QScrollArea): - """ - List of download progress bars. - """ - def __init__(self, common): - super(DownloadList, self).__init__() - self.common = common - - self.downloads = {} - - # The layout that holds all of the downloads - self.downloads_layout = QtWidgets.QVBoxLayout() - self.downloads_layout.setContentsMargins(0, 0, 0, 0) - self.downloads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - - # Wrapper layout that also contains a stretch - wrapper_layout = QtWidgets.QVBoxLayout() - wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - wrapper_layout.addLayout(self.downloads_layout) - wrapper_layout.addStretch() - - # The internal widget of the scroll area - widget = QtWidgets.QWidget() - widget.setLayout(wrapper_layout) - self.setWidget(widget) - self.setWidgetResizable(True) - - # Other scroll area settings - self.setBackgroundRole(QtGui.QPalette.Light) - self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.verticalScrollBar().setValue(maximum) - - def add(self, download_id, content_length): - """ - Add a new download progress bar. - """ - download = Download(self.common, download_id, content_length) - self.downloads[download_id] = download - self.downloads_layout.addWidget(download) - - 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) - download.progress_bar.close() - self.downloads = {} - - -class Downloads(QtWidgets.QWidget): - """ - 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.setMinimumWidth(350) - - # When there are no downloads - empty_image = QtWidgets.QLabel() - empty_image.setAlignment(QtCore.Qt.AlignCenter) - empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png')))) - empty_text = QtWidgets.QLabel(strings._('gui_no_downloads', True)) - empty_text.setAlignment(QtCore.Qt.AlignCenter) - empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) - empty_layout = QtWidgets.QVBoxLayout() - empty_layout.addStretch() - empty_layout.addWidget(empty_image) - empty_layout.addWidget(empty_text) - empty_layout.addStretch() - self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) - self.empty.setLayout(empty_layout) - - # When there are downloads - self.download_list = DownloadList(self.common) - - # Download header - downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) - download_header = QtWidgets.QHBoxLayout() - download_header.addWidget(downloads_label) - download_header.addStretch() - download_header.addWidget(clear_button) - - # Download layout - not_empty_layout = QtWidgets.QVBoxLayout() - not_empty_layout.addLayout(download_header) - not_empty_layout.addWidget(self.download_list) - self.not_empty = QtWidgets.QWidget() - self.not_empty.setLayout(not_empty_layout) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.empty) - layout.addWidget(self.not_empty) - self.setLayout(layout) - - # Reset once at the beginning - self.reset() - - def add(self, download_id, content_length): - """ - Add a new download progress bar. - """ - self.common.log('Downloads', 'add', 'download_id: {}, content_length: {}'.format(download_id, content_length)) - - # Hide empty, show not empty - self.empty.hide() - self.not_empty.show() - - # Add it to the list - self.download_list.add(download_id, content_length) - - def update(self, download_id, downloaded_bytes): - """ - Update the progress of a download progress bar. - """ - self.download_list.update(download_id, downloaded_bytes) - - def cancel(self, download_id): - """ - Update a download progress bar to show that it has been canceled. - """ - self.download_list.cancel(download_id) - - def reset(self): - """ - Reset the downloads back to zero - """ - self.download_list.reset() - - # Hide not empty, show empty - self.not_empty.hide() - self.empty.show() diff --git a/onionshare_gui/share_mode/file_selection.py b/onionshare_gui/share_mode/file_selection.py deleted file mode 100644 index 628ad5ef..00000000 --- a/onionshare_gui/share_mode/file_selection.py +++ /dev/null @@ -1,390 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import os -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - -from ..widgets import Alert, AddFileDialog - -class DropHereLabel(QtWidgets.QLabel): - """ - When there are no files or folders in the FileList yet, display the - 'drop files here' message and graphic. - """ - def __init__(self, common, parent, image=False): - self.parent = parent - super(DropHereLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - - if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) - else: - self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) - - self.hide() - - def dragEnterEvent(self, event): - self.parent.drop_here_image.hide() - self.parent.drop_here_text.hide() - event.accept() - - -class DropCountLabel(QtWidgets.QLabel): - """ - While dragging files over the FileList, this counter displays the - number of files you're dragging. - """ - def __init__(self, common, parent): - self.parent = parent - super(DropCountLabel, self).__init__(parent=parent) - - self.common = common - - self.setAcceptDrops(True) - self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop', True)) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) - self.hide() - - def dragEnterEvent(self, event): - self.hide() - event.accept() - - -class FileList(QtWidgets.QListWidget): - """ - The list of files and folders in the GUI. - """ - files_dropped = QtCore.pyqtSignal() - files_updated = QtCore.pyqtSignal() - - def __init__(self, common, parent=None): - super(FileList, self).__init__(parent) - - self.common = common - - self.setAcceptDrops(True) - self.setIconSize(QtCore.QSize(32, 32)) - self.setSortingEnabled(True) - self.setMinimumHeight(205) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.drop_here_image = DropHereLabel(self.common, self, True) - self.drop_here_text = DropHereLabel(self.common, self, False) - self.drop_count = DropCountLabel(self.common, self) - self.resizeEvent(None) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # file list should have a background image if empty - if self.count() == 0: - self.drop_here_image.show() - self.drop_here_text.show() - else: - self.drop_here_image.hide() - self.drop_here_text.hide() - - def server_started(self): - """ - Update the GUI when the server starts, by hiding delete buttons. - """ - self.setAcceptDrops(False) - self.setCurrentItem(None) - self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - for index in range(self.count()): - self.item(index).item_button.hide() - - def server_stopped(self): - """ - Update the GUI when the server stops, by showing delete buttons. - """ - self.setAcceptDrops(True) - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - for index in range(self.count()): - self.item(index).item_button.show() - - def resizeEvent(self, event): - """ - When the widget is resized, resize the drop files image and text. - """ - offset = 70 - self.drop_here_image.setGeometry(0, 0, self.width(), self.height() - offset) - self.drop_here_text.setGeometry(0, offset, self.width(), self.height() - offset) - - if self.count() > 0: - # Add and delete an empty item, to force all items to get redrawn - # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') - self.addItem(item) - self.takeItem(self.row(item)) - self.update() - - # Extend any filenames that were truncated to fit the window - # We use 200 as a rough guess at how wide the 'file size + delete button' widget is - # and extend based on the overall width minus that amount. - for index in range(self.count()): - metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) - self.item(index).setText(elided) - - - def dragEnterEvent(self, event): - """ - dragEnterEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) - count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) - - size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) - self.drop_count.show() - event.accept() - else: - event.ignore() - - def dragLeaveEvent(self, event): - """ - dragLeaveEvent for dragging files and directories into the widget. - """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) - self.drop_count.hide() - event.accept() - self.update() - - def dragMoveEvent(self, event): - """ - dragMoveEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dropEvent(self, event): - """ - dropEvent for dragging files and directories into the widget. - """ - if event.mimeData().hasUrls: - event.setDropAction(QtCore.Qt.CopyAction) - event.accept() - for url in event.mimeData().urls(): - filename = str(url.toLocalFile()) - self.add_file(filename) - else: - event.ignore() - - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) - self.drop_count.hide() - - self.files_dropped.emit() - - def add_file(self, filename): - """ - Add a file or directory to this widget. - """ - filenames = [] - for index in range(self.count()): - filenames.append(self.item(index).filename) - - if filename not in filenames: - if not os.access(filename, os.R_OK): - Alert(self.common, strings._("not_a_readable_file", True).format(filename)) - return - - fileinfo = QtCore.QFileInfo(filename) - ip = QtWidgets.QFileIconProvider() - icon = ip.icon(fileinfo) - - if os.path.isfile(filename): - size_bytes = fileinfo.size() - size_readable = self.common.human_readable_filesize(size_bytes) - else: - size_bytes = self.common.dir_size(filename) - size_readable = self.common.human_readable_filesize(size_bytes) - - # Create a new item - item = QtWidgets.QListWidgetItem() - item.setIcon(icon) - item.size_bytes = size_bytes - - # Item's filename attribute and size labels - item.filename = filename - item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) - - item.basename = os.path.basename(filename.rstrip('/')) - # Use the basename as the method with which to sort the list - metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) - item.setData(QtCore.Qt.DisplayRole, elided) - - # Item's delete button - def delete_item(): - itemrow = self.row(item) - self.takeItem(itemrow) - self.files_updated.emit() - - item.item_button = QtWidgets.QPushButton() - item.item_button.setDefault(False) - item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) - item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - - # Item info widget, with a white background - item_info_layout = QtWidgets.QHBoxLayout() - item_info_layout.addWidget(item_size) - item_info_layout.addWidget(item.item_button) - item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') - item_info.setLayout(item_info_layout) - - # Create the item's widget and layouts - item_hlayout = QtWidgets.QHBoxLayout() - item_hlayout.addStretch() - item_hlayout.addWidget(item_info) - widget = QtWidgets.QWidget() - widget.setLayout(item_hlayout) - - item.setSizeHint(widget.sizeHint()) - - self.addItem(item) - self.setItemWidget(item, widget) - - self.files_updated.emit() - - -class FileSelection(QtWidgets.QVBoxLayout): - """ - The list of files and folders in the GUI, as well as buttons to add and - delete the files and folders. - """ - def __init__(self, common): - super(FileSelection, self).__init__() - - self.common = common - - self.server_on = False - - # File list - self.file_list = FileList(self.common) - self.file_list.itemSelectionChanged.connect(self.update) - self.file_list.files_dropped.connect(self.update) - self.file_list.files_updated.connect(self.update) - - # Buttons - self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) - self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) - self.delete_button.clicked.connect(self.delete) - button_layout = QtWidgets.QHBoxLayout() - button_layout.addStretch() - button_layout.addWidget(self.add_button) - button_layout.addWidget(self.delete_button) - - # Add the widgets - self.addWidget(self.file_list) - self.addLayout(button_layout) - - self.update() - - def update(self): - """ - Update the GUI elements based on the current state. - """ - # All buttons should be hidden if the server is on - if self.server_on: - self.add_button.hide() - self.delete_button.hide() - else: - self.add_button.show() - - # Delete button should be hidden if item isn't selected - if len(self.file_list.selectedItems()) == 0: - self.delete_button.hide() - else: - self.delete_button.show() - - # Update the file list - self.file_list.update() - - def add(self): - """ - Add button clicked. - """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items', True)) - if file_dialog.exec_() == QtWidgets.QDialog.Accepted: - for filename in file_dialog.selectedFiles(): - self.file_list.add_file(filename) - - self.file_list.setCurrentItem(None) - self.update() - - def delete(self): - """ - Delete button clicked - """ - selected = self.file_list.selectedItems() - for item in selected: - itemrow = self.file_list.row(item) - self.file_list.takeItem(itemrow) - self.file_list.files_updated.emit() - - self.file_list.setCurrentItem(None) - self.update() - - def server_started(self): - """ - Gets called when the server starts. - """ - self.server_on = True - self.file_list.server_started() - self.update() - - def server_stopped(self): - """ - Gets called when the server stops. - """ - self.server_on = False - self.file_list.server_stopped() - self.update() - - def get_num_files(self): - """ - Returns the total number of files and folders in the list. - """ - return len(range(self.file_list.count())) - - def setFocus(self): - """ - Set the Qt app focus on the file selection box. - """ - self.file_list.setFocus() diff --git a/onionshare_gui/share_mode/info.py b/onionshare_gui/share_mode/info.py deleted file mode 100644 index c692649c..00000000 --- a/onionshare_gui/share_mode/info.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class ShareModeInfo(QtWidgets.QWidget): - """ - Share mode information widget - """ - def __init__(self, common, share_mode): - super(ShareModeInfo, self).__init__() - self.common = common - self.share_mode = share_mode - - # Label - self.label_text = "" - self.label = QtWidgets.QLabel() - self.label.setStyleSheet(self.common.css['mode_info_label']) - - # In progress and completed labels - self.in_progress_downloads_count = QtWidgets.QLabel() - self.in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - self.completed_downloads_count = QtWidgets.QLabel() - self.completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - # Toggle button - self.toggle_button = QtWidgets.QPushButton() - self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(35) - self.toggle_button.setFixedHeight(30) - self.toggle_button.setFlat(True) - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.toggle_button.clicked.connect(self.toggle_downloads) - - # Keep track of indicator - self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) - self.update_indicator() - - # Layout - layout = QtWidgets.QHBoxLayout() - layout.addWidget(self.label) - layout.addStretch() - layout.addWidget(self.in_progress_downloads_count) - layout.addWidget(self.completed_downloads_count) - layout.addWidget(self.toggle_button) - self.setLayout(layout) - - self.update_downloads_completed() - self.update_downloads_in_progress() - - def update_label(self, s): - """ - Updates the text of the label. - """ - self.label_text = s - self.label.setText(self.label_text) - - def update_indicator(self, increment=False): - """ - Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. - """ - if increment and not self.share_mode.downloads.isVisible(): - self.indicator_count += 1 - - self.indicator_label.setText("{}".format(self.indicator_count)) - - if self.indicator_count == 0: - self.indicator_label.hide() - else: - size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) - self.indicator_label.show() - - def update_downloads_completed(self): - """ - Update the 'Downloads completed' info widget. - """ - if self.share_mode.downloads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_completed)) - self.completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.share_mode.downloads_completed)) - - def update_downloads_in_progress(self): - """ - Update the 'Downloads in progress' info widget. - """ - if self.share_mode.downloads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_in_progress)) - self.in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.share_mode.downloads_in_progress)) - - def toggle_downloads(self): - """ - Toggle showing and hiding the Downloads widget - """ - self.common.log('ShareModeInfo', 'toggle_downloads') - - if self.share_mode.downloads.isVisible(): - self.share_mode.downloads.hide() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.toggle_button.setFlat(True) - else: - self.share_mode.downloads.show() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) - self.toggle_button.setFlat(False) - - # Reset the indicator count - self.indicator_count = 0 - self.update_indicator() - - self.share_mode.resize_window() - - def show_less(self): - """ - Remove clutter widgets that aren't necessary. - """ - self.label.setText("") - - def show_more(self): - """ - Show all widgets. - """ - self.label.setText(self.label_text) diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py deleted file mode 100644 index d6022746..00000000 --- a/onionshare_gui/share_mode/threads.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore - - -class CompressThread(QtCore.QThread): - """ - Compresses files to be shared - """ - success = QtCore.pyqtSignal() - error = QtCore.pyqtSignal(str) - - def __init__(self, mode): - super(CompressThread, self).__init__() - self.mode = mode - self.mode.common.log('CompressThread', '__init__') - - # prepare files to share - def set_processed_size(self, x): - if self.mode._zip_progress_bar != None: - self.mode._zip_progress_bar.update_processed_size_signal.emit(x) - - def run(self): - self.mode.common.log('CompressThread', 'run') - - try: - if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): - self.success.emit() - else: - # Cancelled - pass - - self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames - except OSError as e: - self.error.emit(e.strerror) - - def cancel(self): - self.mode.common.log('CompressThread', 'cancel') - - # Let the Web and ZipWriter objects know that we're canceling compression early - self.mode.web.cancel_compression = True - if self.mode.web.zip_writer: - self.mode.web.zip_writer.cancel_compression = True diff --git a/setup.py b/setup.py index 94213f7c..86b71f82 100644 --- a/setup.py +++ b/setup.py @@ -69,8 +69,9 @@ setup( 'onionshare', 'onionshare.web', 'onionshare_gui', - 'onionshare_gui.share_mode', - 'onionshare_gui.receive_mode' + 'onionshare_gui.mode', + 'onionshare_gui.mode.share_mode', + 'onionshare_gui.mode.receive_mode' ], include_package_data=True, scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'], -- cgit v1.2.3-54-g00ecf From 5a8cb2ac9dd3eadc5540f42766c779789304f3d1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 15:20:22 -0700 Subject: In ShareMode, remove the ShareModeInfo widget and replace with a customized ToggleHistory widget --- onionshare_gui/mode/share_mode/__init__.py | 54 ++++++++++++------ onionshare_gui/mode/toggle_history.py | 88 ++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 onionshare_gui/mode/toggle_history.py diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index c301037b..f7ba2760 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -28,8 +28,9 @@ from onionshare.web import Web from .file_selection import FileSelection from .downloads import Downloads from .threads import CompressThread -from .info import ShareModeInfo +#from .info import ShareModeInfo from .. import Mode +from ..toggle_history import ToggleHistory from ...widgets import Alert class ShareMode(Mode): @@ -78,7 +79,24 @@ class ShareMode(Mode): self.downloads_completed = 0 # Information about share, and show downloads button - self.info = ShareModeInfo(self.common, self) + #self.info = ShareModeInfo(self.common, self) + + # Info label + self.info_label = QtWidgets.QLabel() + self.info_label.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, self, self.downloads, + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) + ) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.info_label) + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) # Primary action layout self.primary_action_layout.addWidget(self.filesize_warning) @@ -90,7 +108,7 @@ class ShareMode(Mode): # Main layout self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info) + self.main_layout.addLayout(top_bar_layout) self.main_layout.addLayout(self.file_selection) self.main_layout.addWidget(self.primary_action) self.main_layout.addWidget(self.min_width_widget) @@ -191,7 +209,7 @@ class ShareMode(Mode): self.filesize_warning.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 - self.info.update_downloads_in_progress() + #self.info.update_downloads_in_progress() self.file_selection.file_list.adjustSize() def cancel_server_custom(self): @@ -207,7 +225,7 @@ class ShareMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info.show_less() + self.info_label.hide() def handle_request_load(self, event): """ @@ -224,9 +242,9 @@ class ShareMode(Mode): else: filesize = self.web.share_mode.download_filesize self.downloads.add(event["data"]["id"], filesize) - self.info.update_indicator(True) + self.toggle_history.update_indicator(True) self.downloads_in_progress += 1 - self.info.update_downloads_in_progress() + #self.info.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) @@ -242,10 +260,10 @@ class ShareMode(Mode): # Update the total 'completed downloads' info self.downloads_completed += 1 - self.info.update_downloads_completed() + #self.info.update_downloads_completed() # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.info.update_downloads_in_progress() + #self.info.update_downloads_in_progress() # Close on finish? if self.common.settings.get('close_after_first_download'): @@ -256,7 +274,7 @@ class ShareMode(Mode): if self.server_status.status == self.server_status.STATUS_STOPPED: self.downloads.cancel(event["data"]["id"]) self.downloads_in_progress = 0 - self.info.update_downloads_in_progress() + #self.info.update_downloads_in_progress() def handle_request_canceled(self, event): """ @@ -266,7 +284,7 @@ class ShareMode(Mode): # Update the 'in progress downloads' info self.downloads_in_progress -= 1 - self.info.update_downloads_in_progress() + #self.info.update_downloads_in_progress() self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) def on_reload_settings(self): @@ -276,7 +294,7 @@ class ShareMode(Mode): """ if self.server_status.file_selection.get_num_files() > 0: self.primary_action.show() - self.info.show_more() + self.info_label.show() def update_primary_action(self): self.common.log('ShareMode', 'update_primary_action') @@ -285,7 +303,7 @@ class ShareMode(Mode): file_count = self.file_selection.file_list.count() if file_count > 0: self.primary_action.show() - self.info.show_more() + self.info_label.show() # Update the file count in the info label total_size_bytes = 0 @@ -295,13 +313,13 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info.update_label(strings._('gui_file_info', True).format(file_count, total_size_readable)) + self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable)) else: - self.info.update_label(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) + self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable)) else: self.primary_action.hide() - self.info.show_less() + self.info_label.hide() # Resize window self.resize_window() @@ -312,8 +330,8 @@ class ShareMode(Mode): """ self.downloads_completed = 0 self.downloads_in_progress = 0 - self.info.update_downloads_completed() - self.info.update_downloads_in_progress() + #self.info.update_downloads_completed() + #self.info.update_downloads_in_progress() self.downloads.reset() def resize_window(self): diff --git a/onionshare_gui/mode/toggle_history.py b/onionshare_gui/mode/toggle_history.py new file mode 100644 index 00000000..81ecde86 --- /dev/null +++ b/onionshare_gui/mode/toggle_history.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class ToggleHistory(QtWidgets.QPushButton): + """ + Widget for toggling download/upload history on or off, as well as keeping track + of the indicator counter + """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): + super(ToggleHistory, self).__init__() + self.common = common + self.current_mode = current_mode + self.history_widget = history_widget + self.icon = icon + self.selected_icon = selected_icon + + # Toggle button + self.setDefault(False) + self.setFixedWidth(35) + self.setFixedHeight(30) + self.setFlat(True) + self.setIcon(icon) + self.clicked.connect(self.toggle_clicked) + + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel(parent=self) + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Downloads is hidden. + """ + if increment and not self.history_widget.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.show() + + def toggle_clicked(self): + """ + Toggle showing and hiding the history widget + """ + self.common.log('ToggleHistory', 'toggle_clicked') + + if self.history_widget.isVisible(): + self.history_widget.hide() + self.setIcon(self.icon) + self.setFlat(True) + else: + self.history_widget.show() + self.setIcon(self.selected_icon) + self.setFlat(False) + + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + + self.current_mode.resize_window() -- cgit v1.2.3-54-g00ecf From 484c33902fb6ddc6a5262e0e025b8b86c2854283 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 17:35:15 -0700 Subject: Make ShareMode just use a History object directly, instead of defining its own Downloads class --- onionshare_gui/mode/history.py | 328 ++++++++++++++++++++++++++++ onionshare_gui/mode/share_mode/__init__.py | 34 +-- onionshare_gui/mode/share_mode/downloads.py | 248 --------------------- onionshare_gui/mode/toggle_history.py | 88 -------- 4 files changed, 348 insertions(+), 350 deletions(-) create mode 100644 onionshare_gui/mode/history.py delete mode 100644 onionshare_gui/mode/share_mode/downloads.py delete mode 100644 onionshare_gui/mode/toggle_history.py diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py new file mode 100644 index 00000000..31b4a646 --- /dev/null +++ b/onionshare_gui/mode/history.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee + +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 . +""" +import time +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings + + +class HistoryItem(QtWidgets.QWidget): + """ + The base history item + """ + def __init__(self): + super(HistoryItem, self).__init__() + + def update(self): + pass + + def cancel(self): + pass + + +class DownloadHistoryItem(HistoryItem): + """ + Download history item, for share mode + """ + def __init__(self, common, id, total_bytes): + super(DownloadHistoryItem, self).__init__() + self.common = common + + self.id = id + self.started = time.time() + self.total_bytes = total_bytes + self.downloaded_bytes = 0 + + self.setStyleSheet('QWidget { border: 1px solid red; }') + + # 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 + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.progress_bar) + self.setLayout(layout) + + # 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 HistoryItemList(QtWidgets.QScrollArea): + """ + List of items + """ + def __init__(self, common): + super(HistoryItemList, self).__init__() + self.common = common + + self.items = {} + + # The layout that holds all of the items + self.items_layout = QtWidgets.QVBoxLayout() + self.items_layout.setContentsMargins(0, 0, 0, 0) + self.items_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + + # Wrapper layout that also contains a stretch + wrapper_layout = QtWidgets.QVBoxLayout() + wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + wrapper_layout.addLayout(self.items_layout) + wrapper_layout.addStretch() + + # The internal widget of the scroll area + widget = QtWidgets.QWidget() + widget.setLayout(wrapper_layout) + self.setWidget(widget) + self.setWidgetResizable(True) + + # Other scroll area settings + self.setBackgroundRole(QtGui.QPalette.Light) + self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) + + def resizeScroll(self, minimum, maximum): + """ + Scroll to the bottom of the window when the range changes. + """ + self.verticalScrollBar().setValue(maximum) + + def add(self, id, item): + """ + Add a new item. Override this method. + """ + self.items[id] = item + self.items_layout.addWidget(item) + + def update(self, id, data): + """ + Update an item. Override this method. + """ + self.items[id].update(data) + + def cancel(self, id): + """ + Cancel an item. Override this method. + """ + self.items[id].cancel() + + def reset(self): + """ + Reset all items, emptying the list. Override this method. + """ + for item in self.items.values(): + self.items_layout.removeWidget(item) + self.items = {} + + +class History(QtWidgets.QWidget): + """ + A history of what's happened so far in this mode. This contains an internal + object full of a scrollable list of items. + """ + def __init__(self, common, empty_image, empty_text, header_text): + super(History, self).__init__() + self.common = common + + self.setMinimumWidth(350) + + # When there are no items + self.empty_image = QtWidgets.QLabel() + self.empty_image.setAlignment(QtCore.Qt.AlignCenter) + self.empty_image.setPixmap(empty_image) + self.empty_text = QtWidgets.QLabel(empty_text) + self.empty_text.setAlignment(QtCore.Qt.AlignCenter) + self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + empty_layout = QtWidgets.QVBoxLayout() + empty_layout.addStretch() + empty_layout.addWidget(self.empty_image) + empty_layout.addWidget(self.empty_text) + empty_layout.addStretch() + self.empty = QtWidgets.QWidget() + self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setLayout(empty_layout) + + # Header + self.header_label = QtWidgets.QLabel(header_text) + self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + header_layout = QtWidgets.QHBoxLayout() + header_layout.addWidget(self.header_label) + header_layout.addStretch() + header_layout.addWidget(clear_button) + + # When there are items + self.item_list = HistoryItemList(self.common) + self.not_empty_layout = QtWidgets.QVBoxLayout() + self.not_empty_layout.addLayout(header_layout) + self.not_empty_layout.addWidget(self.item_list) + self.not_empty = QtWidgets.QWidget() + self.not_empty.setLayout(self.not_empty_layout) + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.empty) + layout.addWidget(self.not_empty) + self.setLayout(layout) + + # Reset once at the beginning + self.reset() + + def add(self, id, item): + """ + Add a new item. + """ + self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + + # Hide empty, show not empty + self.empty.hide() + self.not_empty.show() + + # Add it to the list + self.item_list.add(id, item) + + + def update(self, id, data): + """ + Update an item. + """ + self.item_list.update(id, data) + + def cancel(self, id): + """ + Cancel an item. + """ + self.item_list.cancel(id) + + def reset(self): + """ + Reset all items. + """ + self.item_list.reset() + + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() + + +class ToggleHistory(QtWidgets.QPushButton): + """ + Widget for toggling showing or hiding the history, as well as keeping track + of the indicator counter if it's hidden + """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): + super(ToggleHistory, self).__init__() + self.common = common + self.current_mode = current_mode + self.history_widget = history_widget + self.icon = icon + self.selected_icon = selected_icon + + # Toggle button + self.setDefault(False) + self.setFixedWidth(35) + self.setFixedHeight(30) + self.setFlat(True) + self.setIcon(icon) + self.clicked.connect(self.toggle_clicked) + + # Keep track of indicator + self.indicator_count = 0 + self.indicator_label = QtWidgets.QLabel(parent=self) + self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.update_indicator() + + def update_indicator(self, increment=False): + """ + Update the display of the indicator count. If increment is True, then + only increment the counter if Downloads is hidden. + """ + if increment and not self.history_widget.isVisible(): + self.indicator_count += 1 + + self.indicator_label.setText("{}".format(self.indicator_count)) + + if self.indicator_count == 0: + self.indicator_label.hide() + else: + size = self.indicator_label.sizeHint() + self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.show() + + def toggle_clicked(self): + """ + Toggle showing and hiding the history widget + """ + self.common.log('ToggleHistory', 'toggle_clicked') + + if self.history_widget.isVisible(): + self.history_widget.hide() + self.setIcon(self.icon) + self.setFlat(True) + else: + self.history_widget.show() + self.setIcon(self.selected_icon) + self.setFlat(False) + + # Reset the indicator count + self.indicator_count = 0 + self.update_indicator() + + self.current_mode.resize_window() diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index f7ba2760..bae4bec8 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -26,13 +26,12 @@ from onionshare.common import Common from onionshare.web import Web from .file_selection import FileSelection -from .downloads import Downloads from .threads import CompressThread -#from .info import ShareModeInfo from .. import Mode -from ..toggle_history import ToggleHistory +from ..history import History, ToggleHistory, DownloadHistoryItem from ...widgets import Alert + class ShareMode(Mode): """ Parts of the main window UI for sharing files. @@ -72,9 +71,14 @@ class ShareMode(Mode): self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) self.filesize_warning.hide() - # Downloads - self.downloads = Downloads(self.common) - self.downloads.hide() + # Download history + self.history = History( + self.common, + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), + strings._('gui_no_downloads'), + strings._('gui_downloads') + ) + self.history.hide() self.downloads_in_progress = 0 self.downloads_completed = 0 @@ -87,7 +91,7 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.downloads, + self.common, self, self.history, QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) @@ -116,7 +120,7 @@ class ShareMode(Mode): # Wrapper layout self.wrapper_layout = QtWidgets.QHBoxLayout() self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.downloads) + self.wrapper_layout.addWidget(self.history) self.setLayout(self.wrapper_layout) # Always start with focus on file selection @@ -241,7 +245,9 @@ class ShareMode(Mode): filesize = self.web.share_mode.gzip_filesize else: filesize = self.web.share_mode.download_filesize - self.downloads.add(event["data"]["id"], filesize) + + item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) + self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.downloads_in_progress += 1 #self.info.update_downloads_in_progress() @@ -252,7 +258,7 @@ class ShareMode(Mode): """ Handle REQUEST_PROGRESS event. """ - self.downloads.update(event["data"]["id"], event["data"]["bytes"]) + self.history.update(event["data"]["id"], event["data"]["bytes"]) # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: @@ -272,7 +278,7 @@ class ShareMode(Mode): self.server_status_label.setText(strings._('closing_automatically', True)) else: if self.server_status.status == self.server_status.STATUS_STOPPED: - self.downloads.cancel(event["data"]["id"]) + self.history.cancel(event["data"]["id"]) self.downloads_in_progress = 0 #self.info.update_downloads_in_progress() @@ -280,7 +286,7 @@ class ShareMode(Mode): """ Handle REQUEST_CANCELED event. """ - self.downloads.cancel(event["data"]["id"]) + self.history.cancel(event["data"]["id"]) # Update the 'in progress downloads' info self.downloads_in_progress -= 1 @@ -332,11 +338,11 @@ class ShareMode(Mode): self.downloads_in_progress = 0 #self.info.update_downloads_completed() #self.info.update_downloads_in_progress() - self.downloads.reset() + self.history.reset() def resize_window(self): min_width = self.common.min_window_width - if self.downloads.isVisible(): + if self.history.isVisible(): min_width += 300 self.adjust_size.emit(min_width) diff --git a/onionshare_gui/mode/share_mode/downloads.py b/onionshare_gui/mode/share_mode/downloads.py deleted file mode 100644 index e78231ad..00000000 --- a/onionshare_gui/mode/share_mode/downloads.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -import time -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class Download(QtWidgets.QWidget): - def __init__(self, common, download_id, total_bytes): - super(Download, self).__init__() - self.common = common - - self.download_id = download_id - self.started = time.time() - self.total_bytes = total_bytes - self.downloaded_bytes = 0 - - self.setStyleSheet('QWidget { border: 1px solid red; }') - - # 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 - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.progress_bar) - self.setLayout(layout) - - # 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 DownloadList(QtWidgets.QScrollArea): - """ - List of download progress bars. - """ - def __init__(self, common): - super(DownloadList, self).__init__() - self.common = common - - self.downloads = {} - - # The layout that holds all of the downloads - self.downloads_layout = QtWidgets.QVBoxLayout() - self.downloads_layout.setContentsMargins(0, 0, 0, 0) - self.downloads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - - # Wrapper layout that also contains a stretch - wrapper_layout = QtWidgets.QVBoxLayout() - wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - wrapper_layout.addLayout(self.downloads_layout) - wrapper_layout.addStretch() - - # The internal widget of the scroll area - widget = QtWidgets.QWidget() - widget.setLayout(wrapper_layout) - self.setWidget(widget) - self.setWidgetResizable(True) - - # Other scroll area settings - self.setBackgroundRole(QtGui.QPalette.Light) - self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.verticalScrollBar().setValue(maximum) - - def add(self, download_id, content_length): - """ - Add a new download progress bar. - """ - download = Download(self.common, download_id, content_length) - self.downloads[download_id] = download - self.downloads_layout.addWidget(download) - - 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) - download.progress_bar.close() - self.downloads = {} - - -class Downloads(QtWidgets.QWidget): - """ - 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.setMinimumWidth(350) - - # When there are no downloads - empty_image = QtWidgets.QLabel() - empty_image.setAlignment(QtCore.Qt.AlignCenter) - empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png')))) - empty_text = QtWidgets.QLabel(strings._('gui_no_downloads', True)) - empty_text.setAlignment(QtCore.Qt.AlignCenter) - empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) - empty_layout = QtWidgets.QVBoxLayout() - empty_layout.addStretch() - empty_layout.addWidget(empty_image) - empty_layout.addWidget(empty_text) - empty_layout.addStretch() - self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) - self.empty.setLayout(empty_layout) - - # When there are downloads - self.download_list = DownloadList(self.common) - - # Download header - downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) - download_header = QtWidgets.QHBoxLayout() - download_header.addWidget(downloads_label) - download_header.addStretch() - download_header.addWidget(clear_button) - - # Download layout - not_empty_layout = QtWidgets.QVBoxLayout() - not_empty_layout.addLayout(download_header) - not_empty_layout.addWidget(self.download_list) - self.not_empty = QtWidgets.QWidget() - self.not_empty.setLayout(not_empty_layout) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.empty) - layout.addWidget(self.not_empty) - self.setLayout(layout) - - # Reset once at the beginning - self.reset() - - def add(self, download_id, content_length): - """ - Add a new download progress bar. - """ - self.common.log('Downloads', 'add', 'download_id: {}, content_length: {}'.format(download_id, content_length)) - - # Hide empty, show not empty - self.empty.hide() - self.not_empty.show() - - # Add it to the list - self.download_list.add(download_id, content_length) - - def update(self, download_id, downloaded_bytes): - """ - Update the progress of a download progress bar. - """ - self.download_list.update(download_id, downloaded_bytes) - - def cancel(self, download_id): - """ - Update a download progress bar to show that it has been canceled. - """ - self.download_list.cancel(download_id) - - def reset(self): - """ - Reset the downloads back to zero - """ - self.download_list.reset() - - # Hide not empty, show empty - self.not_empty.hide() - self.empty.show() diff --git a/onionshare_gui/mode/toggle_history.py b/onionshare_gui/mode/toggle_history.py deleted file mode 100644 index 81ecde86..00000000 --- a/onionshare_gui/mode/toggle_history.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class ToggleHistory(QtWidgets.QPushButton): - """ - Widget for toggling download/upload history on or off, as well as keeping track - of the indicator counter - """ - def __init__(self, common, current_mode, history_widget, icon, selected_icon): - super(ToggleHistory, self).__init__() - self.common = common - self.current_mode = current_mode - self.history_widget = history_widget - self.icon = icon - self.selected_icon = selected_icon - - # Toggle button - self.setDefault(False) - self.setFixedWidth(35) - self.setFixedHeight(30) - self.setFlat(True) - self.setIcon(icon) - self.clicked.connect(self.toggle_clicked) - - # Keep track of indicator - self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel(parent=self) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) - self.update_indicator() - - def update_indicator(self, increment=False): - """ - Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. - """ - if increment and not self.history_widget.isVisible(): - self.indicator_count += 1 - - self.indicator_label.setText("{}".format(self.indicator_count)) - - if self.indicator_count == 0: - self.indicator_label.hide() - else: - size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) - self.indicator_label.show() - - def toggle_clicked(self): - """ - Toggle showing and hiding the history widget - """ - self.common.log('ToggleHistory', 'toggle_clicked') - - if self.history_widget.isVisible(): - self.history_widget.hide() - self.setIcon(self.icon) - self.setFlat(True) - else: - self.history_widget.show() - self.setIcon(self.selected_icon) - self.setFlat(False) - - # Reset the indicator count - self.indicator_count = 0 - self.update_indicator() - - self.current_mode.resize_window() -- cgit v1.2.3-54-g00ecf From bc573209d9be256822bee00dec6c1376ee197a98 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 18:09:02 -0700 Subject: Delete Info widget, and move completed and in progress widgets into the header of history --- onionshare_gui/mode/history.py | 68 ++++++++++--- onionshare_gui/mode/share_mode/__init__.py | 40 +++----- onionshare_gui/mode/share_mode/info.py | 149 ----------------------------- share/locale/en.json | 4 +- 4 files changed, 72 insertions(+), 189 deletions(-) delete mode 100644 onionshare_gui/mode/share_mode/info.py diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 31b4a646..a28340a4 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -179,6 +179,30 @@ class History(QtWidgets.QWidget): self.setMinimumWidth(350) + # In progress and completed counters + self.in_progress_count = 0 + self.completed_count = 0 + + # In progress and completed labels + self.in_progress_label = QtWidgets.QLabel() + self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label = QtWidgets.QLabel() + self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + + # Header + self.header_label = QtWidgets.QLabel(header_text) + self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) + clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) + clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) + clear_button.setFlat(True) + clear_button.clicked.connect(self.reset) + header_layout = QtWidgets.QHBoxLayout() + header_layout.addWidget(self.header_label) + header_layout.addStretch() + header_layout.addWidget(self.in_progress_label) + header_layout.addWidget(self.completed_label) + header_layout.addWidget(clear_button) + # When there are no items self.empty_image = QtWidgets.QLabel() self.empty_image.setAlignment(QtCore.Qt.AlignCenter) @@ -195,22 +219,9 @@ class History(QtWidgets.QWidget): self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) self.empty.setLayout(empty_layout) - # Header - self.header_label = QtWidgets.QLabel(header_text) - self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) - header_layout = QtWidgets.QHBoxLayout() - header_layout.addWidget(self.header_label) - header_layout.addStretch() - header_layout.addWidget(clear_button) - # When there are items self.item_list = HistoryItemList(self.common) self.not_empty_layout = QtWidgets.QVBoxLayout() - self.not_empty_layout.addLayout(header_layout) self.not_empty_layout.addWidget(self.item_list) self.not_empty = QtWidgets.QWidget() self.not_empty.setLayout(self.not_empty_layout) @@ -218,12 +229,15 @@ class History(QtWidgets.QWidget): # Layout layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) + layout.addLayout(header_layout) layout.addWidget(self.empty) layout.addWidget(self.not_empty) self.setLayout(layout) # Reset once at the beginning self.reset() + self.update_completed() + self.update_in_progress() def add(self, id, item): """ @@ -261,6 +275,34 @@ class History(QtWidgets.QWidget): self.not_empty.hide() self.empty.show() + # Reset counters + self.completed_count = 0 + self.in_progress_count = 0 + self.update_completed() + self.update_in_progress() + + def update_completed(self): + """ + Update the 'completed' widget. + """ + if self.completed_count == 0: + image = self.common.get_resource_path('images/share_completed_none.png') + else: + image = self.common.get_resource_path('images/share_completed.png') + self.completed_label.setText(' {1:d}'.format(image, self.completed_count)) + self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + + def update_in_progress(self): + """ + Update the 'in progress' widget. + """ + if self.in_progress_count == 0: + image = self.common.get_resource_path('images/share_in_progress_none.png') + else: + image = self.common.get_resource_path('images/share_in_progress.png') + self.in_progress_label.setText(' {1:d}'.format(image, self.in_progress_count)) + self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip', True).format(self.in_progress_count)) + class ToggleHistory(QtWidgets.QPushButton): """ diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index bae4bec8..0bf094c0 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -79,11 +79,6 @@ class ShareMode(Mode): strings._('gui_downloads') ) self.history.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - - # Information about share, and show downloads button - #self.info = ShareModeInfo(self.common, self) # Info label self.info_label = QtWidgets.QLabel() @@ -211,9 +206,9 @@ class ShareMode(Mode): self._zip_progress_bar = None self.filesize_warning.hide() - self.downloads_in_progress = 0 - self.downloads_completed = 0 - #self.info.update_downloads_in_progress() + self.history.in_progress_count = 0 + self.history.completed_count = 0 + self.history.update_in_progress() self.file_selection.file_list.adjustSize() def cancel_server_custom(self): @@ -249,8 +244,8 @@ class ShareMode(Mode): item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) - self.downloads_in_progress += 1 - #self.info.update_downloads_in_progress() + self.history.in_progress_count += 1 + self.history.update_in_progress() self.system_tray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True)) @@ -264,12 +259,11 @@ class ShareMode(Mode): 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)) - # Update the total 'completed downloads' info - self.downloads_completed += 1 - #self.info.update_downloads_completed() - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - #self.info.update_downloads_in_progress() + # Update completed and in progress labels + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() # Close on finish? if self.common.settings.get('close_after_first_download'): @@ -279,8 +273,8 @@ class ShareMode(Mode): else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.history.cancel(event["data"]["id"]) - self.downloads_in_progress = 0 - #self.info.update_downloads_in_progress() + self.history.in_progress_count = 0 + self.history.update_in_progress() def handle_request_canceled(self, event): """ @@ -288,9 +282,9 @@ class ShareMode(Mode): """ self.history.cancel(event["data"]["id"]) - # Update the 'in progress downloads' info - self.downloads_in_progress -= 1 - #self.info.update_downloads_in_progress() + # Update in progress count + self.history.in_progress_count -= 1 + self.history.update_in_progress() self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) def on_reload_settings(self): @@ -334,10 +328,6 @@ class ShareMode(Mode): """ Set the info counters back to zero. """ - self.downloads_completed = 0 - self.downloads_in_progress = 0 - #self.info.update_downloads_completed() - #self.info.update_downloads_in_progress() self.history.reset() def resize_window(self): diff --git a/onionshare_gui/mode/share_mode/info.py b/onionshare_gui/mode/share_mode/info.py deleted file mode 100644 index c692649c..00000000 --- a/onionshare_gui/mode/share_mode/info.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class ShareModeInfo(QtWidgets.QWidget): - """ - Share mode information widget - """ - def __init__(self, common, share_mode): - super(ShareModeInfo, self).__init__() - self.common = common - self.share_mode = share_mode - - # Label - self.label_text = "" - self.label = QtWidgets.QLabel() - self.label.setStyleSheet(self.common.css['mode_info_label']) - - # In progress and completed labels - self.in_progress_downloads_count = QtWidgets.QLabel() - self.in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - self.completed_downloads_count = QtWidgets.QLabel() - self.completed_downloads_count.setStyleSheet(self.common.css['mode_info_label']) - - # Toggle button - self.toggle_button = QtWidgets.QPushButton() - self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(35) - self.toggle_button.setFixedHeight(30) - self.toggle_button.setFlat(True) - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.toggle_button.clicked.connect(self.toggle_downloads) - - # Keep track of indicator - self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) - self.update_indicator() - - # Layout - layout = QtWidgets.QHBoxLayout() - layout.addWidget(self.label) - layout.addStretch() - layout.addWidget(self.in_progress_downloads_count) - layout.addWidget(self.completed_downloads_count) - layout.addWidget(self.toggle_button) - self.setLayout(layout) - - self.update_downloads_completed() - self.update_downloads_in_progress() - - def update_label(self, s): - """ - Updates the text of the label. - """ - self.label_text = s - self.label.setText(self.label_text) - - def update_indicator(self, increment=False): - """ - Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. - """ - if increment and not self.share_mode.downloads.isVisible(): - self.indicator_count += 1 - - self.indicator_label.setText("{}".format(self.indicator_count)) - - if self.indicator_count == 0: - self.indicator_label.hide() - else: - size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) - self.indicator_label.show() - - def update_downloads_completed(self): - """ - Update the 'Downloads completed' info widget. - """ - if self.share_mode.downloads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_completed)) - self.completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(self.share_mode.downloads_completed)) - - def update_downloads_in_progress(self): - """ - Update the 'Downloads in progress' info widget. - """ - if self.share_mode.downloads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_downloads_count.setText(' {1:d}'.format(image, self.share_mode.downloads_in_progress)) - self.in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(self.share_mode.downloads_in_progress)) - - def toggle_downloads(self): - """ - Toggle showing and hiding the Downloads widget - """ - self.common.log('ShareModeInfo', 'toggle_downloads') - - if self.share_mode.downloads.isVisible(): - self.share_mode.downloads.hide() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')) ) - self.toggle_button.setFlat(True) - else: - self.share_mode.downloads.show() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) ) - self.toggle_button.setFlat(False) - - # Reset the indicator count - self.indicator_count = 0 - self.update_indicator() - - self.share_mode.resize_window() - - def show_less(self): - """ - Remove clutter widgets that aren't necessary. - """ - self.label.setText("") - - def show_more(self): - """ - Show all widgets. - """ - self.label.setText(self.label_text) diff --git a/share/locale/en.json b/share/locale/en.json index c7beb6ba..3537b0a2 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -151,8 +151,8 @@ "gui_status_indicator_receive_started": "Receiving", "gui_file_info": "{} files, {}", "gui_file_info_single": "{} file, {}", - "info_in_progress_downloads_tooltip": "{} download(s) in progress", - "info_completed_downloads_tooltip": "{} download(s) completed", + "history_in_progress_tooltip": "{} in progress", + "history_completed_tooltip": "{} completed", "info_in_progress_uploads_tooltip": "{} upload(s) in progress", "info_completed_uploads_tooltip": "{} upload(s) completed", "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", -- cgit v1.2.3-54-g00ecf From 38e62d85288c9947f089b6ba80dc3146178d2a3a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 18:20:13 -0700 Subject: The History header is now only shown if there are items again, and the clear history button resets everything. Also, reset hides individual items because, for some reason, they still show up otherwise. --- onionshare_gui/mode/history.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index a28340a4..0f8ccdca 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -165,6 +165,7 @@ class HistoryItemList(QtWidgets.QScrollArea): """ for item in self.items.values(): self.items_layout.removeWidget(item) + item.hide() self.items = {} @@ -222,6 +223,7 @@ class History(QtWidgets.QWidget): # When there are items self.item_list = HistoryItemList(self.common) self.not_empty_layout = QtWidgets.QVBoxLayout() + self.not_empty_layout.addLayout(header_layout) self.not_empty_layout.addWidget(self.item_list) self.not_empty = QtWidgets.QWidget() self.not_empty.setLayout(self.not_empty_layout) @@ -229,15 +231,12 @@ class History(QtWidgets.QWidget): # Layout layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) - layout.addLayout(header_layout) layout.addWidget(self.empty) layout.addWidget(self.not_empty) self.setLayout(layout) # Reset once at the beginning self.reset() - self.update_completed() - self.update_in_progress() def add(self, id, item): """ @@ -252,7 +251,6 @@ class History(QtWidgets.QWidget): # Add it to the list self.item_list.add(id, item) - def update(self, id, data): """ Update an item. -- cgit v1.2.3-54-g00ecf From 1b1ade63daf937b5de9e40f61fb5f523ca56c81a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 18:49:09 -0700 Subject: Start fixing the GUI tests. Also, refactor CommonTests to pass in a Mode object instead of the string "share" or "receive" --- tests_gui_local/commontests.py | 182 ++++++--------------- ...onshare_share_mode_download_test_public_mode.py | 38 ++--- 2 files changed, 69 insertions(+), 151 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index 21e8cfad..5ceee668 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -6,6 +6,9 @@ import zipfile from PyQt5 import QtCore, QtTest from onionshare import strings +from onionshare_gui.mode.receive_mode import ReceiveMode +from onionshare_gui.mode.share_mode import ShareMode + class CommonTests(object): def test_gui_loaded(self): @@ -24,71 +27,42 @@ class CommonTests(object): '''Test that the status bar is visible''' self.assertTrue(self.gui.status_bar.isVisible()) - def test_info_widget_shows_less(self, mode): - '''Test that minimum information (no label) is displayed in the info bar''' - if mode == 'share': - self.assertFalse(self.gui.share_mode.info.label.text() == "") - if mode == 'receive': - # There's no minimal display in receive mode - self.assertTrue(False) - def test_click_mode(self, mode): '''Test that we can switch Mode by clicking the button''' - if mode == 'receive': + if type(mode) == ReceiveMode: QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if mode == 'share': + if type(mode) == ShareMode: QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) def test_click_toggle_history(self, mode): '''Test that we can toggle Download or Upload history by clicking the toggle button''' - if mode == 'receive': - currently_visible = self.gui.receive_mode.uploads.isVisible() - QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.receive_mode.uploads.isVisible(), not currently_visible) - if mode == 'share': - currently_visible = self.gui.receive_mode.uploads.isVisible() - QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.downloads.isVisible(), not currently_visible) + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) def test_history_indicator(self, mode, public_mode): '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - if mode == 'receive': - # Make sure history is toggled off - if self.gui.receive_mode.uploads.isVisible(): - QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.receive_mode.uploads.isVisible()) + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) - # Indicator should not be visible yet - self.assertFalse(self.gui.receive_mode.info.indicator_label.isVisible()) + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + if type(mode) == ReceiveMode: # Upload a file files = {'file[]': open('/tmp/test.txt', 'rb')} if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) else: path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) QtTest.QTest.qWait(2000) - # Indicator should be visible, have a value of "1" - self.assertTrue(self.gui.receive_mode.info.indicator_label.isVisible()) - self.assertEqual(self.gui.receive_mode.info.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(self.gui.receive_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.receive_mode.info.indicator_label.isVisible()) - - if mode == 'share': - # Make sure history is toggled off - if self.gui.share_mode.downloads.isVisible(): - QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.downloads.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(self.gui.share_mode.info.indicator_label.isVisible()) - + if type(mode) == ShareMode: # Download files if public_mode: url = "http://127.0.0.1:{}/download".format(self.gui.app.port) @@ -97,44 +71,31 @@ class CommonTests(object): r = requests.get(url) QtTest.QTest.qWait(2000) - # Indicator should be visible, have a value of "1" - self.assertTrue(self.gui.share_mode.info.indicator_label.isVisible()) - self.assertEqual(self.gui.share_mode.info.indicator_label.text(), "1") + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(self.gui.share_mode.info.toggle_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.info.indicator_label.isVisible()) + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) def test_history_is_not_visible(self, mode): '''Test that the History section is not visible''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.uploads.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.downloads.isVisible()) + self.assertFalse(mode.history.isVisible()) def test_history_is_visible(self, mode): '''Test that the History section is visible''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.uploads.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(mode.history.isVisible()) def test_server_working_on_start_button_pressed(self, mode): '''Test we can start the service''' # Should be in SERVER_WORKING state - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.receive_mode.server_status.status, 1) - if mode == 'share': - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.status, 1) + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) def test_server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) - if mode == 'share': - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) def test_settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' @@ -144,10 +105,7 @@ class CommonTests(object): '''Test that the server has started''' QtTest.QTest.qWait(2000) # Should now be in SERVER_STARTED state - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 2) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 2) + self.assertEqual(mode.server_status.status, 2) def test_a_web_server_is_running(self): '''Test that the web server has started''' @@ -157,38 +115,26 @@ class CommonTests(object): def test_have_a_slug(self, mode, public_mode): '''Test that we have a valid slug''' - if mode == 'receive': - if not public_mode: - self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') - if mode == 'share': - if not public_mode: - self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') def test_url_description_shown(self, mode): '''Test that the URL label is showing''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + self.assertTrue(mode.server_status.url_description.isVisible()) def test_have_copy_url_button(self, mode): '''Test that the Copy URL button is shown''' - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + self.assertTrue(mode.server_status.copy_url_button.isVisible()) def test_server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) - if mode == 'share': - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) def test_web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' @@ -197,10 +143,7 @@ class CommonTests(object): s.connect(('127.0.0.1', self.gui.app.port)) if not public_mode: - if mode == 'receive': - path = '/{}'.format(self.gui.receive_mode.server_status.web.slug) - if mode == 'share': - path = '/{}'.format(self.gui.share_mode.server_status.web.slug) + path = '/{}'.format(mode.server_status.web.slug) else: path = '/' @@ -223,29 +166,18 @@ class CommonTests(object): def test_history_widgets_present(self, mode): '''Test that the relevant widgets are present in the history view after activity has taken place''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.uploads.empty.isVisible()) - self.assertTrue(self.gui.receive_mode.uploads.not_empty.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.downloads.empty.isVisible()) - self.assertTrue(self.gui.share_mode.downloads.not_empty.isVisible()) + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) def test_counter_incremented(self, mode, count): '''Test that the counter has incremented''' - if mode == 'receive': - self.assertEquals(self.gui.receive_mode.uploads_completed, count) - if mode == 'share': - self.assertEquals(self.gui.share_mode.downloads_completed, count) + self.assertEquals(mode.uploads_completed, count) def test_server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' - if mode == 'receive': - QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - if stay_open: - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(self.gui.share_mode.server_status.status, 0) + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) def test_web_service_is_stopped(self): '''Test that the web server also stopped''' @@ -257,9 +189,9 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' - if mode == 'receive': + if type(mode) == ReceiveMode: self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) - if mode == 'share': + if type(mode) == ShareMode: if stay_open: self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped', True)) else: @@ -269,28 +201,18 @@ class CommonTests(object): def test_set_timeout(self, mode, timeout): '''Test that the timeout can be set''' timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - if mode == 'receive': - self.gui.receive_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.receive_mode.server_status.shutdown_timeout.dateTime(), timer) - if mode == 'share': - self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) def test_timeout_widget_hidden(self, mode): '''Test that the timeout widget is hidden when share has started''' - if mode == 'receive': - self.assertFalse(self.gui.receive_mode.server_status.shutdown_timeout_container.isVisible()) - if mode == 'share': - self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) def test_server_timed_out(self, mode, wait): '''Test that the server has timed out after the timer ran out''' QtTest.QTest.qWait(wait) # We should have timed out now - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 0) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 0) + self.assertEqual(mode.server_status.status, 0) # Receive-specific tests def test_upload_file(self, public_mode, expected_file): diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index a10ee4c2..82f1989c 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -96,21 +96,17 @@ class OnionShareGuiTest(unittest.TestCase): def test_file_selection_widget_has_a_file(self): CommonTests.test_file_selection_widget_has_a_file(self) - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - @pytest.mark.run(order=7) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') + CommonTests.test_history_is_not_visible(self, self.gui.share_mode) @pytest.mark.run(order=8) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') + CommonTests.test_click_toggle_history(self, self.gui.share_mode) @pytest.mark.run(order=9) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + CommonTests.test_history_is_visible(self, self.gui.share_mode) @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): @@ -126,11 +122,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): @@ -142,7 +138,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=17) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=18) def test_a_web_server_is_running(self): @@ -150,23 +146,23 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=19) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) + CommonTests.test_have_a_slug(self, self.gui.share_mode, True) @pytest.mark.run(order=20) def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') + CommonTests.test_url_description_shown(self, self.gui.share_mode) @pytest.mark.run(order=21) def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') + CommonTests.test_have_copy_url_button(self, self.gui.share_mode) @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') + CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) @pytest.mark.run(order=23) def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) + CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True) @pytest.mark.run(order=24) def test_download_share(self): @@ -174,11 +170,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=25) def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') + CommonTests.test_history_widgets_present(self, self.gui.share_mode) @pytest.mark.run(order=26) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) + CommonTests.test_server_is_stopped(self, self.gui.share_mode, False) @pytest.mark.run(order=27) def test_web_service_is_stopped(self): @@ -186,7 +182,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=28) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False) @pytest.mark.run(order=29) def test_add_button_visible(self): @@ -194,9 +190,9 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=30) def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - CommonTests.test_history_indicator(self, 'share', True) + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + CommonTests.test_a_server_is_started(self, self.gui.share_mode) + CommonTests.test_history_indicator(self, self.gui.share_mode, True) if __name__ == "__main__": -- cgit v1.2.3-54-g00ecf From 4d217e84032cd9af64d1a1371ce75a08a5de47e9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 20:37:54 -0700 Subject: Refactor CommonTests to pass in actual Mode objects, and fix all tests. Now all ShareMode tests pass --- tests_gui_local/commontests.py | 2 +- .../onionshare_receive_mode_upload_test.py | 36 +++++++++---------- ...onshare_receive_mode_upload_test_public_mode.py | 36 +++++++++---------- .../onionshare_share_mode_download_test.py | 38 +++++++++----------- ...nionshare_share_mode_download_test_stay_open.py | 42 ++++++++++------------ tests_gui_local/onionshare_slug_persistent_test.py | 38 +++++++++----------- tests_gui_local/onionshare_timer_test.py | 18 ++++------ 7 files changed, 97 insertions(+), 113 deletions(-) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index 5ceee668..d311c7bb 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -171,7 +171,7 @@ class CommonTests(object): def test_counter_incremented(self, mode, count): '''Test that the counter has incremented''' - self.assertEquals(mode.uploads_completed, count) + self.assertEquals(mode.history.completed_count, count) def test_server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 19674aa3..1ce91ba2 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -96,27 +96,27 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=6) def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') + CommonTests.test_click_mode(self, self.gui.receive_mode) @pytest.mark.run(order=6) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'receive') + CommonTests.test_history_is_not_visible(self, self.gui.receive_mode) @pytest.mark.run(order=7) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'receive') + CommonTests.test_click_toggle_history(self, self.gui.receive_mode) @pytest.mark.run(order=8) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') + CommonTests.test_history_is_visible(self, self.gui.receive_mode) @pytest.mark.run(order=8) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) @pytest.mark.run(order=9) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode) @pytest.mark.run(order=10) def test_settings_button_is_hidden(self): @@ -124,7 +124,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=11) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') + CommonTests.test_a_server_is_started(self, self.gui.receive_mode) @pytest.mark.run(order=12) def test_a_web_server_is_running(self): @@ -132,23 +132,23 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=14) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', False) + CommonTests.test_have_a_slug(self, self.gui.receive_mode, False) @pytest.mark.run(order=15) def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') + CommonTests.test_url_description_shown(self, self.gui.receive_mode) @pytest.mark.run(order=16) def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') + CommonTests.test_have_copy_url_button(self, self.gui.receive_mode) @pytest.mark.run(order=17) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') + CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode) @pytest.mark.run(order=18) def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) + CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', False) @pytest.mark.run(order=19) def test_upload_file(self): @@ -156,11 +156,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=20) def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') + CommonTests.test_history_widgets_present(self, self.gui.receive_mode) @pytest.mark.run(order=21) def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) + CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1) @pytest.mark.run(order=22) def test_upload_same_file_is_renamed(self): @@ -168,15 +168,15 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=23) def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) + CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2) @pytest.mark.run(order=24) def test_history_indicator(self): - CommonTests.test_history_indicator(self, 'receive', False) + CommonTests.test_history_indicator(self, self.gui.receive_mode, False) @pytest.mark.run(order=25) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) + CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False) @pytest.mark.run(order=26) def test_web_service_is_stopped(self): @@ -184,7 +184,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=27) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index e3f85731..6591a884 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -96,27 +96,27 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=5) def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') + CommonTests.test_click_mode(self, self.gui.receive_mode) @pytest.mark.run(order=6) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'receive') + CommonTests.test_history_is_not_visible(self, self.gui.receive_mode) @pytest.mark.run(order=7) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'receive') + CommonTests.test_click_toggle_history(self, self.gui.receive_mode) @pytest.mark.run(order=8) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') + CommonTests.test_history_is_visible(self, self.gui.receive_mode) @pytest.mark.run(order=9) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) @pytest.mark.run(order=10) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode) @pytest.mark.run(order=11) def test_settings_button_is_hidden(self): @@ -124,7 +124,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=12) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') + CommonTests.test_a_server_is_started(self, self.gui.receive_mode) @pytest.mark.run(order=13) def test_a_web_server_is_running(self): @@ -132,23 +132,23 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=14) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', True) + CommonTests.test_have_a_slug(self, self.gui.receive_mode, True) @pytest.mark.run(order=15) def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') + CommonTests.test_url_description_shown(self, self.gui.receive_mode) @pytest.mark.run(order=16) def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') + CommonTests.test_have_copy_url_button(self, self.gui.receive_mode) @pytest.mark.run(order=17) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') + CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode) @pytest.mark.run(order=18) def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) + CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', True) @pytest.mark.run(order=19) def test_upload_file(self): @@ -156,11 +156,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=20) def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') + CommonTests.test_history_widgets_present(self, self.gui.receive_mode) @pytest.mark.run(order=21) def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) + CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1) @pytest.mark.run(order=22) def test_upload_same_file_is_renamed(self): @@ -168,15 +168,15 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=23) def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) + CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2) @pytest.mark.run(order=24) def test_history_indicator(self): - CommonTests.test_history_indicator(self, 'receive', True) + CommonTests.test_history_indicator(self, self.gui.receive_mode, True) @pytest.mark.run(order=25) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) + CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False) @pytest.mark.run(order=26) def test_web_service_is_stopped(self): @@ -184,7 +184,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=27) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index c4a60101..6842f1a6 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -96,21 +96,17 @@ class OnionShareGuiTest(unittest.TestCase): def test_file_selection_widget_has_a_file(self): CommonTests.test_file_selection_widget_has_a_file(self) - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - @pytest.mark.run(order=7) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') + CommonTests.test_history_is_not_visible(self, self.gui.share_mode) @pytest.mark.run(order=8) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') + CommonTests.test_click_toggle_history(self, self.gui.share_mode) @pytest.mark.run(order=9) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + CommonTests.test_history_is_visible(self, self.gui.share_mode) @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): @@ -126,11 +122,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): @@ -142,7 +138,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=17) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=18) def test_a_web_server_is_running(self): @@ -150,23 +146,23 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=19) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) + CommonTests.test_have_a_slug(self, self.gui.share_mode, False) @pytest.mark.run(order=20) def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') + CommonTests.test_url_description_shown(self, self.gui.share_mode) @pytest.mark.run(order=21) def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') + CommonTests.test_have_copy_url_button(self, self.gui.share_mode) @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') + CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) @pytest.mark.run(order=23) def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', False) + CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', False) @pytest.mark.run(order=24) def test_download_share(self): @@ -174,11 +170,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=25) def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') + CommonTests.test_history_widgets_present(self, self.gui.share_mode) @pytest.mark.run(order=26) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) + CommonTests.test_server_is_stopped(self, self.gui.share_mode, False) @pytest.mark.run(order=27) def test_web_service_is_stopped(self): @@ -186,7 +182,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=28) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False) @pytest.mark.run(order=29) def test_add_button_visible(self): @@ -194,9 +190,9 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=30) def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - CommonTests.test_history_indicator(self, 'share', False) + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + CommonTests.test_a_server_is_started(self, self.gui.share_mode) + CommonTests.test_history_indicator(self, self.gui.share_mode, False) if __name__ == "__main__": diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index 8426c264..df9bc857 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -96,21 +96,17 @@ class OnionShareGuiTest(unittest.TestCase): def test_file_selection_widget_has_a_file(self): CommonTests.test_file_selection_widget_has_a_file(self) - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - @pytest.mark.run(order=7) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') + CommonTests.test_history_is_not_visible(self, self.gui.share_mode) @pytest.mark.run(order=8) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') + CommonTests.test_click_toggle_history(self, self.gui.share_mode) @pytest.mark.run(order=9) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + CommonTests.test_history_is_visible(self, self.gui.share_mode) @pytest.mark.run(order=10) def test_deleting_only_file_hides_delete_button(self): @@ -126,11 +122,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=13) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) @pytest.mark.run(order=14) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) @pytest.mark.run(order=15) def test_add_delete_buttons_hidden(self): @@ -142,7 +138,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=17) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=18) def test_a_web_server_is_running(self): @@ -150,23 +146,23 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=19) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) + CommonTests.test_have_a_slug(self, self.gui.share_mode, True) @pytest.mark.run(order=20) def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') + CommonTests.test_url_description_shown(self, self.gui.share_mode) @pytest.mark.run(order=21) def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') + CommonTests.test_have_copy_url_button(self, self.gui.share_mode) @pytest.mark.run(order=22) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') + CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) @pytest.mark.run(order=23) def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) + CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True) @pytest.mark.run(order=24) def test_download_share(self): @@ -174,11 +170,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=25) def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') + CommonTests.test_history_widgets_present(self, self.gui.share_mode) @pytest.mark.run(order=26) def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'share', 1) + CommonTests.test_counter_incremented(self, self.gui.share_mode, 1) @pytest.mark.run(order=27) def test_download_share_again(self): @@ -186,11 +182,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=28) def test_counter_incremented_again(self): - CommonTests.test_counter_incremented(self, 'share', 2) + CommonTests.test_counter_incremented(self, self.gui.share_mode, 2) @pytest.mark.run(order=29) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) @pytest.mark.run(order=30) def test_web_service_is_stopped(self): @@ -198,7 +194,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=31) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True) @pytest.mark.run(order=32) def test_add_button_visible(self): @@ -206,9 +202,9 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=33) def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - CommonTests.test_history_indicator(self, 'share', True) + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + CommonTests.test_a_server_is_started(self, self.gui.share_mode) + CommonTests.test_history_indicator(self, self.gui.share_mode, True) if __name__ == "__main__": diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index 9fb623dd..5b825dad 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -94,29 +94,25 @@ class OnionShareGuiTest(unittest.TestCase): def test_server_status_bar_is_visible(self): CommonTests.test_server_status_bar_is_visible(self) - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - @pytest.mark.run(order=7) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') + CommonTests.test_history_is_not_visible(self, self.gui.share_mode) @pytest.mark.run(order=8) def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') + CommonTests.test_click_toggle_history(self, self.gui.share_mode) @pytest.mark.run(order=9) def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') + CommonTests.test_history_is_visible(self, self.gui.share_mode) @pytest.mark.run(order=10) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) @pytest.mark.run(order=11) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) @pytest.mark.run(order=12) def test_settings_button_is_hidden(self): @@ -124,7 +120,7 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=13) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=14) def test_a_web_server_is_running(self): @@ -132,17 +128,17 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=15) def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) + CommonTests.test_have_a_slug(self, self.gui.share_mode, False) global slug slug = self.gui.share_mode.server_status.web.slug @pytest.mark.run(order=16) def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') + CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) @pytest.mark.run(order=17) def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) @pytest.mark.run(order=18) def test_web_service_is_stopped(self): @@ -150,13 +146,13 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=19) def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) + CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True) @pytest.mark.run(order=20) def test_server_started_again(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_server_status_indicator_says_starting(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=21) def test_have_same_slug(self): @@ -165,14 +161,14 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=22) def test_server_is_stopped_again(self): - CommonTests.test_server_is_stopped(self, 'share', True) + CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) CommonTests.test_web_service_is_stopped(self) @pytest.mark.run(order=23) def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - CommonTests.test_history_indicator(self, 'share', False) + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + CommonTests.test_a_server_is_started(self, self.gui.share_mode) + CommonTests.test_history_indicator(self, self.gui.share_mode, False) if __name__ == "__main__": diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py index 701d9a21..4aaaf364 100644 --- a/tests_gui_local/onionshare_timer_test.py +++ b/tests_gui_local/onionshare_timer_test.py @@ -96,29 +96,25 @@ class OnionShareGuiTest(unittest.TestCase): def test_file_selection_widget_has_a_file(self): CommonTests.test_file_selection_widget_has_a_file(self) - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - @pytest.mark.run(order=7) def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') + CommonTests.test_history_is_not_visible(self, self.gui.share_mode) @pytest.mark.run(order=8) def test_set_timeout(self): - CommonTests.test_set_timeout(self, 'share', 5) + CommonTests.test_set_timeout(self, self.gui.share_mode, 5) @pytest.mark.run(order=9) def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') + CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) @pytest.mark.run(order=10) def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') + CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) @pytest.mark.run(order=11) def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') + CommonTests.test_a_server_is_started(self, self.gui.share_mode) @pytest.mark.run(order=12) def test_a_web_server_is_running(self): @@ -126,11 +122,11 @@ class OnionShareGuiTest(unittest.TestCase): @pytest.mark.run(order=13) def test_timeout_widget_hidden(self): - CommonTests.test_timeout_widget_hidden(self, 'share') + CommonTests.test_timeout_widget_hidden(self, self.gui.share_mode) @pytest.mark.run(order=14) def test_timeout(self): - CommonTests.test_server_timed_out(self, 'share', 10000) + CommonTests.test_server_timed_out(self, self.gui.share_mode, 10000) @pytest.mark.run(order=15) def test_web_service_is_stopped(self): -- cgit v1.2.3-54-g00ecf From c9beb694f2aaf3d9afcc7272f73eaf4eabe6a603 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 21:14:20 -0700 Subject: Update ReceiveMode to use History directly, and now all GUI tests pass --- onionshare_gui/mode/history.py | 182 ++++++++++++ onionshare_gui/mode/receive_mode/__init__.py | 101 +++---- onionshare_gui/mode/receive_mode/uploads.py | 395 --------------------------- 3 files changed, 236 insertions(+), 442 deletions(-) delete mode 100644 onionshare_gui/mode/receive_mode/uploads.py diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 0f8ccdca..4f5b2cef 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -18,6 +18,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ import time +import subprocess +from datetime import datetime from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings @@ -103,6 +105,186 @@ class DownloadHistoryItem(HistoryItem): self.started) +class UploadHistoryItemFile(QtWidgets.QWidget): + def __init__(self, common, filename): + super(UploadHistoryItemFile, self).__init__() + self.common = common + + self.common.log('UploadHistoryItemFile', '__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('UploadHistoryItemFile', '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('UploadHistoryItemFile', '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('UploadHistoryItemFile', 'open_folder', 'not implemented for Windows yet') + + +class UploadHistoryItem(HistoryItem): + def __init__(self, common, id, content_length): + super(UploadHistoryItem, self).__init__() + self.common = common + self.id = id + self.content_length = content_length + self.started = datetime.now() + + # Label + self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress', True).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, data): + """ + Using the progress from Web, update the progress bar and file size labels + for each file + """ + if data['action'] == 'progress': + total_uploaded_bytes = 0 + for filename in data['progress']: + total_uploaded_bytes += data['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(data['progress']): + # Add a new file if needed + if filename not in self.files: + self.files[filename] = UploadHistoryItemFile(self.common, filename) + self.files_layout.addWidget(self.files[filename]) + + # Update the file + self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + + elif data['action'] == 'rename': + self.files[data['old_filename']].rename(data['new_filename']) + self.files[data['new_filename']] = self.files.pop(data['old_filename']) + + elif data['action'] == 'finished': + # 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', True).format( + self.started.strftime("%b %d, %I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%I:%M%p") + ) + else: + text = strings._('gui_upload_finished_range', True).format( + self.started.strftime("%b %d, %I:%M%p"), + self.ended.strftime("%b %d, %I:%M%p") + ) + self.label.setText(text) + + class HistoryItemList(QtWidgets.QScrollArea): """ List of items diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 96c76dbf..ffa259e7 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -22,8 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.web import Web -from .uploads import Uploads -from .info import ReceiveModeInfo +from ..history import History, ToggleHistory, UploadHistoryItem from .. import Mode class ReceiveMode(Mode): @@ -47,26 +46,36 @@ class ReceiveMode(Mode): self.server_status.web = self.web self.server_status.update() - # Uploads - self.uploads = Uploads(self.common) - self.uploads.hide() - self.uploads_in_progress = 0 - self.uploads_completed = 0 - self.new_upload = False # For scrolling to the bottom of the uploads list - - # Information about share, and show uploads button - self.info = ReceiveModeInfo(self.common, self) - self.info.show_less() - - # Receive mode info - self.receive_info = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) - self.receive_info.setMinimumHeight(80) - self.receive_info.setWordWrap(True) + # Upload history + self.history = History( + self.common, + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png'))), + strings._('gui_no_uploads'), + strings._('gui_uploads') + ) + self.history.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, self, self.history, + QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) + ) + + # Receive mode warning + receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning', True)) + receive_warning.setMinimumHeight(80) + receive_warning.setWordWrap(True) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) # Main layout self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.addWidget(self.info) - self.main_layout.addWidget(self.receive_info) + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addWidget(receive_warning) self.main_layout.addWidget(self.primary_action) self.main_layout.addStretch() self.main_layout.addWidget(self.min_width_widget) @@ -74,7 +83,7 @@ class ReceiveMode(Mode): # Wrapper layout self.wrapper_layout = QtWidgets.QHBoxLayout() self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.uploads) + self.wrapper_layout.addWidget(self.history) self.setLayout(self.wrapper_layout) def get_stop_server_shutdown_timeout_text(self): @@ -114,7 +123,7 @@ class ReceiveMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info.show_less() + #self.info.show_less() def handle_request_load(self, event): """ @@ -126,10 +135,11 @@ class ReceiveMode(Mode): """ Handle REQUEST_STARTED event. """ - self.uploads.add(event["data"]["id"], event["data"]["content_length"]) - self.info.update_indicator(True) - self.uploads_in_progress += 1 - self.info.update_uploads_in_progress() + item = UploadHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + self.history.add(event["data"]["id"], item) + self.toggle_history.update_indicator(True) + self.history.in_progress_count += 1 + self.history.update_in_progress() self.system_tray.showMessage(strings._('systray_upload_started_title', True), strings._('systray_upload_started_message', True)) @@ -137,7 +147,10 @@ class ReceiveMode(Mode): """ Handle REQUEST_PROGRESS event. """ - self.uploads.update(event["data"]["id"], event["data"]["progress"]) + self.history.update(event["data"]["id"], { + 'action': 'progress', + 'progress': event["data"]["progress"] + }) def handle_request_close_server(self, event): """ @@ -150,51 +163,45 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - self.uploads.rename(event["data"]["id"], event["data"]["old_filename"], event["data"]["new_filename"]) + self.history.update(event["data"]["id"], { + 'action': 'rename', + 'old_filename': event["data"]["old_filename"], + 'new_filename': event["data"]["new_filename"] + }) def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. """ - self.uploads.finished(event["data"]["id"]) - # Update the total 'completed uploads' info - self.uploads_completed += 1 - self.info.update_uploads_completed() - # Update the 'in progress uploads' info - self.uploads_in_progress -= 1 - self.info.update_uploads_in_progress() + self.history.update(event["data"]["id"], { + 'action': 'finished' + }) + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() def on_reload_settings(self): """ We should be ok to re-enable the 'Start Receive Mode' button now. """ self.primary_action.show() - self.info.show_more() + #self.info.show_more() def reset_info_counters(self): """ Set the info counters back to zero. """ - self.uploads_completed = 0 - self.uploads_in_progress = 0 - self.info.update_uploads_completed() - self.info.update_uploads_in_progress() - self.uploads.reset() + self.history.reset() def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') - # Show the info widget when the server is active - if self.server_status.status == self.server_status.STATUS_STARTED: - self.info.show_more() - else: - self.info.show_less() - # Resize window self.resize_window() def resize_window(self): min_width = self.common.min_window_width - if self.uploads.isVisible(): + if self.history.isVisible(): min_width += 300 self.adjust_size.emit(min_width) diff --git a/onionshare_gui/mode/receive_mode/uploads.py b/onionshare_gui/mode/receive_mode/uploads.py deleted file mode 100644 index c445be47..00000000 --- a/onionshare_gui/mode/receive_mode/uploads.py +++ /dev/null @@ -1,395 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -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', True).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', True).format( - self.started.strftime("%b %d, %I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range', True).format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range', True).format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%b %d, %I:%M%p") - ) - self.label.setText(text) - - -class UploadList(QtWidgets.QScrollArea): - """ - List of upload progess bars. - """ - def __init__(self, common): - super(UploadList, self).__init__() - self.common = common - - self.uploads = {} - - # The layout that holds all of the uploads - self.uploads_layout = QtWidgets.QVBoxLayout() - self.uploads_layout.setContentsMargins(0, 0, 0, 0) - self.uploads_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - - # Wrapper layout that also contains a stretch - wrapper_layout = QtWidgets.QVBoxLayout() - wrapper_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) - wrapper_layout.addLayout(self.uploads_layout) - wrapper_layout.addStretch() - - # The internal widget of the scroll area - widget = QtWidgets.QWidget() - widget.setLayout(wrapper_layout) - self.setWidget(widget) - self.setWidgetResizable(True) - - # Other scroll area settings - self.setBackgroundRole(QtGui.QPalette.Light) - self.verticalScrollBar().rangeChanged.connect(self.resizeScroll) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.verticalScrollBar().setValue(maximum) - - def add(self, upload_id, content_length): - """ - Add a new upload progress bar. - """ - 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 - """ - for upload in self.uploads.values(): - self.uploads_layout.removeWidget(upload) - upload.progress_bar.close() - self.uploads = {} - - -class Uploads(QtWidgets.QWidget): - """ - 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.setMinimumWidth(350) - - # When there are no uploads - empty_image = QtWidgets.QLabel() - empty_image.setAlignment(QtCore.Qt.AlignCenter) - empty_image.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png')))) - empty_text = QtWidgets.QLabel(strings._('gui_no_uploads', True)) - empty_text.setAlignment(QtCore.Qt.AlignCenter) - empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) - empty_layout = QtWidgets.QVBoxLayout() - empty_layout.addStretch() - empty_layout.addWidget(empty_image) - empty_layout.addWidget(empty_text) - empty_layout.addStretch() - self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) - self.empty.setLayout(empty_layout) - - # When there are uploads - self.upload_list = UploadList(self.common) - - # Upload header - uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True)) - uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history', True)) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) - upload_header = QtWidgets.QHBoxLayout() - upload_header.addWidget(uploads_label) - upload_header.addStretch() - upload_header.addWidget(clear_button) - - # Upload layout - not_empty_layout = QtWidgets.QVBoxLayout() - not_empty_layout.addLayout(upload_header) - not_empty_layout.addWidget(self.upload_list) - self.not_empty = QtWidgets.QWidget() - self.not_empty.setLayout(not_empty_layout) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.empty) - layout.addWidget(self.not_empty) - self.setLayout(layout) - - # Reset once at the beginning - self.reset() - - 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 empty, show not empty - self.empty.hide() - self.not_empty.show() - - # Add it to the list - self.upload_list.add(upload_id, content_length) - - def update(self, upload_id, progress): - """ - Update the progress of an upload. - """ - self.upload_list.update(upload_id, progress) - - def rename(self, upload_id, old_filename, new_filename): - """ - Rename a file, which happens if the filename already exists in downloads_dir. - """ - self.upload_list.rename(upload_id, old_filename, new_filename) - - def finished(self, upload_id): - """ - An upload has finished. - """ - self.upload_list.finished(upload_id) - - 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.upload_list.cancel(upload_id) - - def reset(self): - """ - Reset the uploads back to zero - """ - self.upload_list.reset() - - # Hide not empty, show empty - self.not_empty.hide() - self.empty.show() -- cgit v1.2.3-54-g00ecf From 656784dfa94627121c8b75c588f555a61a223ae3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 21:28:10 -0700 Subject: Remove obsolete ReceiveModeInfo file --- onionshare_gui/mode/receive_mode/info.py | 136 ------------------------------- 1 file changed, 136 deletions(-) delete mode 100644 onionshare_gui/mode/receive_mode/info.py diff --git a/onionshare_gui/mode/receive_mode/info.py b/onionshare_gui/mode/receive_mode/info.py deleted file mode 100644 index c23f8496..00000000 --- a/onionshare_gui/mode/receive_mode/info.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class ReceiveModeInfo(QtWidgets.QWidget): - """ - Receive mode information widget - """ - def __init__(self, common, receive_mode): - super(ReceiveModeInfo, self).__init__() - self.common = common - self.receive_mode = receive_mode - - # In progress and completed labels - self.in_progress_uploads_count = QtWidgets.QLabel() - self.in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - self.completed_uploads_count = QtWidgets.QLabel() - self.completed_uploads_count.setStyleSheet(self.common.css['mode_info_label']) - - # Toggle button - self.toggle_button = QtWidgets.QPushButton() - self.toggle_button.setDefault(False) - self.toggle_button.setFixedWidth(35) - self.toggle_button.setFixedHeight(30) - self.toggle_button.setFlat(True) - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.toggle_button.clicked.connect(self.toggle_uploads) - - # Keep track of indicator - self.indicator_count = 0 - self.indicator_label = QtWidgets.QLabel(parent=self.toggle_button) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) - self.update_indicator() - - # Layout - layout = QtWidgets.QHBoxLayout() - layout.addStretch() - layout.addWidget(self.in_progress_uploads_count) - layout.addWidget(self.completed_uploads_count) - layout.addWidget(self.toggle_button) - self.setLayout(layout) - - self.update_uploads_completed() - self.update_uploads_in_progress() - - def update_indicator(self, increment=False): - """ - Update the display of the indicator count. If increment is True, then - only increment the counter if Uploads is hidden. - """ - if increment and not self.receive_mode.uploads.isVisible(): - self.indicator_count += 1 - - self.indicator_label.setText("{}".format(self.indicator_count)) - - if self.indicator_count == 0: - self.indicator_label.hide() - else: - size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) - self.indicator_label.show() - - def update_uploads_completed(self): - """ - Update the 'Uploads completed' info widget. - """ - if self.receive_mode.uploads_completed == 0: - image = self.common.get_resource_path('images/share_completed_none.png') - else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_completed)) - self.completed_uploads_count.setToolTip(strings._('info_completed_uploads_tooltip', True).format(self.receive_mode.uploads_completed)) - - def update_uploads_in_progress(self): - """ - Update the 'Uploads in progress' info widget. - """ - if self.receive_mode.uploads_in_progress == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') - else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_uploads_count.setText(' {1:d}'.format(image, self.receive_mode.uploads_in_progress)) - self.in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.receive_mode.uploads_in_progress)) - - def toggle_uploads(self): - """ - Toggle showing and hiding the Uploads widget - """ - self.common.log('ReceiveModeInfo', 'toggle_uploads') - - if self.receive_mode.uploads.isVisible(): - self.receive_mode.uploads.hide() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')) ) - self.toggle_button.setFlat(True) - else: - self.receive_mode.uploads.show() - self.toggle_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) ) - self.toggle_button.setFlat(False) - - # Reset the indicator count - self.indicator_count = 0 - self.update_indicator() - - self.receive_mode.resize_window() - - def show_less(self): - """ - Remove clutter widgets that aren't necessary. - """ - pass - - def show_more(self): - """ - Show all widgets. - """ - pass -- cgit v1.2.3-54-g00ecf From bc8759bc77864fceeab22e0d4038ce0750c3cc07 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 21:46:16 -0700 Subject: Properly close items inside the item list, instead of just hiding them --- onionshare_gui/mode/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 4f5b2cef..ff31e3a9 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -347,7 +347,7 @@ class HistoryItemList(QtWidgets.QScrollArea): """ for item in self.items.values(): self.items_layout.removeWidget(item) - item.hide() + item.close() self.items = {} -- cgit v1.2.3-54-g00ecf From 49e371d503ff05f95aeef358118fd74ba39a58fe Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 8 Oct 2018 10:59:11 +1100 Subject: adjust widget sizes when switching mode --- onionshare_gui/onionshare_gui.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 6a7eb63a..35088ebe 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -200,12 +200,14 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode.hide() + self.adjust_size(self.common.min_window_width) self.share_mode.show() else: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.share_mode.hide() + self.adjust_size(self.common.min_window_width) self.receive_mode.show() self.update_server_status_indicator() -- cgit v1.2.3-54-g00ecf From 50c0d91c5751c96d1dfc51055ed37ff166abe416 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 21:57:45 -0700 Subject: Missing imports --- onionshare_gui/mode/history.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index ff31e3a9..07121363 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -19,10 +19,12 @@ along with this program. If not, see . """ import time import subprocess +import os from datetime import datetime from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings +from ..widgets import Alert class HistoryItem(QtWidgets.QWidget): -- cgit v1.2.3-54-g00ecf From 5a2ca669a111604f46201627ea420a59ec9a9899 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 22:07:19 -0700 Subject: Rip out all of the adjust size logic and let Qt just handle it --- onionshare_gui/mode/__init__.py | 25 +--------------- onionshare_gui/mode/receive_mode/__init__.py | 9 ------ onionshare_gui/mode/share_mode/__init__.py | 9 ------ onionshare_gui/onionshare_gui.py | 43 +--------------------------- 4 files changed, 2 insertions(+), 84 deletions(-) diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index cfbb235b..0971ff32 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -36,7 +36,6 @@ class Mode(QtWidgets.QWidget): starting_server_step3 = QtCore.pyqtSignal() starting_server_error = QtCore.pyqtSignal(str) set_server_active = QtCore.pyqtSignal(bool) - adjust_size = QtCore.pyqtSignal(int) def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): super(Mode, self).__init__() @@ -50,8 +49,6 @@ class Mode(QtWidgets.QWidget): self.filenames = filenames - self.setMinimumWidth(self.common.min_window_width) - # The web object gets created in init() self.web = None @@ -83,7 +80,7 @@ class Mode(QtWidgets.QWidget): # Hack to allow a minimum width on the main layout # Note: It's up to the downstream Mode to add this to its layout self.min_width_widget = QtWidgets.QWidget() - self.min_width_widget.setMinimumWidth(self.common.min_window_width) + self.min_width_widget.setMinimumWidth(600) def init(self): """ @@ -332,23 +329,3 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_FINISHED event. """ pass - - def resize_window(self): - """ - We call this to force the OnionShare window to resize itself to be smaller. - For this to do anything, the Mode needs to override it and call: - - self.adjust_size.emit(min_width) - - It can calculate min_width (the new minimum window width) based on what - widgets are visible. - """ - pass - - def show(self): - """ - Always resize the window after showing this Mode widget. - """ - super(Mode, self).show() - self.qtapp.processEvents() - self.resize_window() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index ffa259e7..66e0bbe7 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -196,12 +196,3 @@ class ReceiveMode(Mode): def update_primary_action(self): self.common.log('ReceiveMode', 'update_primary_action') - - # Resize window - self.resize_window() - - def resize_window(self): - min_width = self.common.min_window_width - if self.history.isVisible(): - min_width += 300 - self.adjust_size.emit(min_width) diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 0bf094c0..b3d7c549 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -321,21 +321,12 @@ class ShareMode(Mode): self.primary_action.hide() self.info_label.hide() - # Resize window - self.resize_window() - def reset_info_counters(self): """ Set the info counters back to zero. """ self.history.reset() - def resize_window(self): - min_width = self.common.min_window_width - if self.history.isVisible(): - min_width += 300 - self.adjust_size.emit(min_width) - @staticmethod def _compute_total_size(filenames): total_size = 0 diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 35088ebe..9a71ae28 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -45,7 +45,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') - self.common.min_window_width = 460 + self.setMinimumWidth(700) self.onion = onion self.qtapp = qtapp @@ -133,7 +133,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.share_mode.server_status.url_copied.connect(self.copy_url) self.share_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.share_mode.set_server_active.connect(self.set_server_active) - self.share_mode.adjust_size.connect(self.adjust_size) # Receive mode self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only) @@ -148,7 +147,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode.server_status.url_copied.connect(self.copy_url) self.receive_mode.server_status.hidservauth_copied.connect(self.copy_hidservauth) self.receive_mode.set_server_active.connect(self.set_server_active) - self.receive_mode.adjust_size.connect(self.adjust_size) self.update_mode_switcher() self.update_server_status_indicator() @@ -169,9 +167,6 @@ class OnionShareGui(QtWidgets.QMainWindow): self.setCentralWidget(central_widget) self.show() - # Adjust window size, to start with a minimum window width - self.adjust_size(self.common.min_window_width) - # The server isn't active yet self.set_server_active(False) @@ -200,14 +195,12 @@ class OnionShareGui(QtWidgets.QMainWindow): self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode.hide() - self.adjust_size(self.common.min_window_width) self.share_mode.show() else: self.share_mode_button.setStyleSheet(self.common.css['mode_switcher_unselected_style']) self.receive_mode_button.setStyleSheet(self.common.css['mode_switcher_selected_style']) self.share_mode.hide() - self.adjust_size(self.common.min_window_width) self.receive_mode.show() self.update_server_status_indicator() @@ -450,40 +443,6 @@ class OnionShareGui(QtWidgets.QMainWindow): # Disable settings menu action when server is active self.settings_action.setEnabled(not active) - def adjust_size(self, min_width): - """ - Recursively adjust size on all widgets. min_width is the new minimum width - of the window. - """ - self.setMinimumWidth(min_width) - - def adjust_size_layout(layout): - count = layout.count() - for i in range(count): - item = layout.itemAt(i) - if item: - child_widget = item.widget() - if child_widget: - adjust_size_widget(child_widget) - child_layout = item.layout() - if child_layout: - adjust_size_layout(child_layout) - - def adjust_size_widget(widget): - layout = widget.layout() - if layout: - adjust_size_layout(layout) - widget.adjustSize() - - # Adjust sizes of each mode - for mode in [self.share_mode, self.receive_mode]: - self.qtapp.processEvents() - adjust_size_widget(mode) - - # Adjust window size - self.qtapp.processEvents() - self.adjustSize() - def closeEvent(self, e): self.common.log('OnionShareGui', 'closeEvent') try: -- cgit v1.2.3-54-g00ecf From 9aa982563ba2bfe71d56221d5137e857d1425fe2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 7 Oct 2018 22:09:57 -0700 Subject: Remove one more reference to resize_window --- onionshare_gui/mode/history.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 07121363..cf944aa0 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -548,5 +548,3 @@ class ToggleHistory(QtWidgets.QPushButton): # Reset the indicator count self.indicator_count = 0 self.update_indicator() - - self.current_mode.resize_window() -- cgit v1.2.3-54-g00ecf From 6227c6cbc540078deaff627a1c70e907009dff00 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 9 Oct 2018 20:51:10 -0700 Subject: Set min width and height so everything always looks good, and change onion address to monospace font --- onionshare/common.py | 1 + onionshare_gui/onionshare_gui.py | 3 ++- onionshare_gui/server_status.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index fb3b1e7a..96f9d2ad 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -211,6 +211,7 @@ class Common(object): color: #000000; padding: 10px; border: 1px solid #666666; + font-size: 12px; } """, diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 9a71ae28..3175f0f9 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -45,7 +45,8 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') - self.setMinimumWidth(700) + self.setMinimumWidth(820) + self.setMinimumHeight(530) self.onion = onion self.qtapp = qtapp diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 32135ca4..99aaa9f1 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -90,20 +90,20 @@ class ServerStatus(QtWidgets.QWidget): self.server_button.clicked.connect(self.server_button_clicked) # URL layout - url_font = QtGui.QFont() + url_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) self.url_description = QtWidgets.QLabel() self.url_description.setWordWrap(True) self.url_description.setMinimumHeight(50) self.url = QtWidgets.QLabel() self.url.setFont(url_font) self.url.setWordWrap(True) - self.url.setMinimumHeight(65) self.url.setMinimumSize(self.url.sizeHint()) self.url.setStyleSheet(self.common.css['server_status_url']) self.copy_url_button = QtWidgets.QPushButton(strings._('gui_copy_url', True)) self.copy_url_button.setFlat(True) self.copy_url_button.setStyleSheet(self.common.css['server_status_url_buttons']) + self.copy_url_button.setMinimumHeight(65) self.copy_url_button.clicked.connect(self.copy_url) self.copy_hidservauth_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.copy_hidservauth_button.setFlat(True) -- cgit v1.2.3-54-g00ecf From b982a9a24895f14dcb92289f7010e1a80d453557 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 9 Oct 2018 21:15:42 -0700 Subject: Actually, the window needs to be taller --- onionshare_gui/onionshare_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3175f0f9..1e03bc3e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -46,7 +46,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') self.setMinimumWidth(820) - self.setMinimumHeight(530) + self.setMinimumHeight(620) self.onion = onion self.qtapp = qtapp @@ -154,7 +154,7 @@ class OnionShareGui(QtWidgets.QMainWindow): # Layouts contents_layout = QtWidgets.QVBoxLayout() - contents_layout.setContentsMargins(10, 10, 10, 10) + contents_layout.setContentsMargins(10, 0, 10, 0) contents_layout.addWidget(self.receive_mode) contents_layout.addWidget(self.share_mode) -- cgit v1.2.3-54-g00ecf From 90172c913b025d011e05794902a55e4f478ffc76 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 9 Oct 2018 21:18:26 -0700 Subject: Stop hiding the share mode info label when tor breaks --- onionshare_gui/mode/receive_mode/__init__.py | 1 - onionshare_gui/mode/share_mode/__init__.py | 1 - 2 files changed, 2 deletions(-) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 66e0bbe7..e312a55b 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -123,7 +123,6 @@ class ReceiveMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - #self.info.show_less() def handle_request_load(self, event): """ diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index b3d7c549..1c1f33ae 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -224,7 +224,6 @@ class ShareMode(Mode): Connection to Tor broke. """ self.primary_action.hide() - self.info_label.hide() def handle_request_load(self, event): """ -- cgit v1.2.3-54-g00ecf From 5616a6a965b8662e20b5608788e3e17a1eb22af0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 9 Oct 2018 21:49:05 -0700 Subject: Make the history indicator label circular again --- onionshare/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/common.py b/onionshare/common.py index 96f9d2ad..cab1e747 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -287,7 +287,7 @@ class Common(object): font-weight: bold; font-size: 10px; padding: 2px; - border-radius: 8px; + border-radius: 7px; text-align: center; }""", -- cgit v1.2.3-54-g00ecf From 56e5c8b90879d7c7053e506fe9d913736717539a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 9 Oct 2018 22:21:03 -0700 Subject: Add "download started" date/time to download history progress bars --- onionshare_gui/mode/history.py | 7 +++++-- share/locale/en.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index cf944aa0..8cfa0ed5 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -50,11 +50,13 @@ class DownloadHistoryItem(HistoryItem): self.common = common self.id = id - self.started = time.time() self.total_bytes = total_bytes self.downloaded_bytes = 0 + self.started = time.time() + self.started_dt = datetime.fromtimestamp(self.started) - self.setStyleSheet('QWidget { border: 1px solid red; }') + # Label + self.label = QtWidgets.QLabel(strings._('gui_download_in_progress').format(self.started_dt.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -69,6 +71,7 @@ class DownloadHistoryItem(HistoryItem): # Layout layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.label) layout.addWidget(self.progress_bar) self.setLayout(layout) diff --git a/share/locale/en.json b/share/locale/en.json index 3537b0a2..e5d9a3be 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -180,5 +180,6 @@ "gui_upload_in_progress": "Upload Started {}", "gui_upload_finished_range": "Uploaded {} to {}", "gui_upload_finished": "Uploaded {}", + "gui_download_in_progress": "Download Started {}", "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}" } -- cgit v1.2.3-54-g00ecf From 85de803fda9a94e8721c94b2448043c6a30a1a03 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 10 Oct 2018 16:49:42 +1100 Subject: Raise minimumHeight again to account for overlap issues on MacOS caused by Mac's Qt widget padding --- onionshare_gui/onionshare_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 1e03bc3e..e672d74e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -46,7 +46,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') self.setMinimumWidth(820) - self.setMinimumHeight(620) + self.setMinimumHeight(650) self.onion = onion self.qtapp = qtapp -- cgit v1.2.3-54-g00ecf From beda37df06722cfa6163897a9f2ad3388084886b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 10 Oct 2018 18:09:43 +1100 Subject: Remove commented out obsolete code --- onionshare_gui/mode/receive_mode/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index e312a55b..b73acca2 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -185,7 +185,6 @@ class ReceiveMode(Mode): We should be ok to re-enable the 'Start Receive Mode' button now. """ self.primary_action.show() - #self.info.show_more() def reset_info_counters(self): """ -- cgit v1.2.3-54-g00ecf From 753380663b5344e0b653ac8279143764b17ed465 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 10 Oct 2018 18:16:08 -0700 Subject: Final few tweaks to make this look perfect in macOS --- onionshare_gui/mode/share_mode/file_selection.py | 3 ++- onionshare_gui/onionshare_gui.py | 2 +- onionshare_gui/server_status.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py index d59df234..6bfa7dbf 100644 --- a/onionshare_gui/mode/share_mode/file_selection.py +++ b/onionshare_gui/mode/share_mode/file_selection.py @@ -89,7 +89,7 @@ class FileList(QtWidgets.QListWidget): self.setAcceptDrops(True) self.setIconSize(QtCore.QSize(32, 32)) self.setSortingEnabled(True) - self.setMinimumHeight(205) + self.setMinimumHeight(160) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.drop_here_image = DropHereLabel(self.common, self, True) self.drop_here_text = DropHereLabel(self.common, self, False) @@ -261,6 +261,7 @@ class FileList(QtWidgets.QListWidget): # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() + item_info_layout.setContentsMargins(0, 0, 0, 0) item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index e672d74e..c2e6657b 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -46,7 +46,7 @@ class OnionShareGui(QtWidgets.QMainWindow): self.common = common self.common.log('OnionShareGui', '__init__') self.setMinimumWidth(820) - self.setMinimumHeight(650) + self.setMinimumHeight(660) self.onion = onion self.qtapp = qtapp diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index 99aaa9f1..0267d826 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -142,12 +142,12 @@ class ServerStatus(QtWidgets.QWidget): When the widget is resized, try and adjust the display of a v3 onion URL. """ try: - self.get_url() + # Wrap the URL label url_length=len(self.get_url()) if url_length > 60: width = self.frameGeometry().width() if width < 530: - wrapped_onion_url = textwrap.fill(self.get_url(), 50) + wrapped_onion_url = textwrap.fill(self.get_url(), 46) self.url.setText(wrapped_onion_url) else: self.url.setText(self.get_url()) -- cgit v1.2.3-54-g00ecf From 235fb84dfc42c441b10e8d4da86165e15556e83c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 10 Oct 2018 18:45:55 -0700 Subject: Fix various bugs so local GUI tests pass again after merges --- onionshare_gui/mode/history.py | 12 ++++++------ onionshare_gui/mode/receive_mode/__init__.py | 2 +- tests_gui_local/commontests.py | 4 +++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 8cfa0ed5..b446b9fb 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -196,7 +196,7 @@ class UploadHistoryItem(HistoryItem): self.started = datetime.now() # 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 self.progress_bar = QtWidgets.QProgressBar() @@ -274,16 +274,16 @@ class UploadHistoryItem(HistoryItem): 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', True).format( + text = strings._('gui_upload_finished').format( self.started.strftime("%b %d, %I:%M%p") ) 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.ended.strftime("%I:%M%p") ) 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.ended.strftime("%b %d, %I:%M%p") ) @@ -380,7 +380,7 @@ class History(QtWidgets.QWidget): # Header self.header_label = QtWidgets.QLabel(header_text) 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.setFlat(True) clear_button.clicked.connect(self.reset) @@ -486,7 +486,7 @@ class History(QtWidgets.QWidget): else: image = self.common.get_resource_path('images/share_in_progress.png') self.in_progress_label.setText(' {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): diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index d22dd149..f070f963 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -63,7 +63,7 @@ class ReceiveMode(Mode): ) # 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.setWordWrap(True) diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py index c057386d..27e406c1 100644 --- a/tests_gui_local/commontests.py +++ b/tests_gui_local/commontests.py @@ -14,6 +14,8 @@ 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.receive_mode import ReceiveMode class CommonTests(object): @@ -236,7 +238,7 @@ class CommonTests(object): def test_server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' 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 stay_open: self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) -- cgit v1.2.3-54-g00ecf From a6483d281593c1d6fbbe39bb35202c7ef871bf23 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 11 Oct 2018 15:09:27 +1100 Subject: Refactor local tests to reuse most of their code --- tests_gui_local/GuiBaseTest.py | 428 +++++++++++++++++++++ tests_gui_local/__init__.py | 2 +- tests_gui_local/commontests.py | 339 ---------------- .../onionshare_receive_mode_upload_test.py | 117 +----- ...onshare_receive_mode_upload_test_public_mode.py | 117 +----- .../onionshare_share_mode_download_test.py | 129 +------ ...onshare_share_mode_download_test_public_mode.py | 129 +------ ...nionshare_share_mode_download_test_stay_open.py | 142 +------ tests_gui_local/onionshare_slug_persistent_test.py | 99 +---- tests_gui_local/onionshare_timer_test.py | 66 +--- 10 files changed, 491 insertions(+), 1077 deletions(-) create mode 100644 tests_gui_local/GuiBaseTest.py delete mode 100644 tests_gui_local/commontests.py diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py new file mode 100644 index 00000000..e7d25031 --- /dev/null +++ b/tests_gui_local/GuiBaseTest.py @@ -0,0 +1,428 @@ +import os +import requests +import socket +import socks +import zipfile +import json +import shutil + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + + +class GuiBaseTest(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): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if type(mode) == ReceiveMode: + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if type(mode) == ShareMode: + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) + + def test_history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + def test_history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + self.assertFalse(mode.history.isVisible()) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible''' + self.assertTrue(mode.history.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + self.assertTrue(mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + self.assertEquals(mode.history.completed_count, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if type(mode) == ReceiveMode: + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + if type(mode) == ShareMode: + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + # Auto-stop timer tests + def test_set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest + + def run_all_common_setup_tests(self): + GuiBaseTest.test_gui_loaded(self) + GuiBaseTest.test_windowTitle_seen(self) + GuiBaseTest.test_settings_button_is_visible(self) + GuiBaseTest.test_server_status_bar_is_visible(self) + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + GuiBaseTest.test_click_mode(self, self.gui.share_mode) + GuiBaseTest.test_file_selection_widget_has_a_file(self) + GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) + GuiBaseTest.test_deleting_only_file_hides_delete_button(self) + GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) + GuiBaseTest.test_file_selection_widget_readd_files(self) + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) + GuiBaseTest.test_add_delete_buttons_hidden(self) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) + GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + GuiBaseTest.test_download_share(self, public_mode) + GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_add_button_visible(self) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) + GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) + GuiBaseTest.test_web_service_is_stopped(self) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + GuiBaseTest.test_click_mode(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) + GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') + GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py index bb2b2182..7cf168eb 100644 --- a/tests_gui_local/__init__.py +++ b/tests_gui_local/__init__.py @@ -1 +1 @@ -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest diff --git a/tests_gui_local/commontests.py b/tests_gui_local/commontests.py deleted file mode 100644 index 27e406c1..00000000 --- a/tests_gui_local/commontests.py +++ /dev/null @@ -1,339 +0,0 @@ -import os -import requests -import socket -import socks -import zipfile -import json -import shutil - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - - -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): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - def test_click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if type(mode) == ReceiveMode: - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if type(mode) == ShareMode: - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - def test_click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' - currently_visible = mode.history.isVisible() - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertEqual(mode.history.isVisible(), not currently_visible) - - def test_history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) - else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) - r = requests.get(url) - QtTest.QTest.qWait(2000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - def test_history_is_not_visible(self, mode): - '''Test that the History section is not visible''' - self.assertFalse(mode.history.isVisible()) - - def test_history_is_visible(self, mode): - '''Test that the History section is visible''' - self.assertTrue(mode.history.isVisible()) - - def test_server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 1) - - def test_server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - def test_a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - - - def test_url_description_shown(self, mode): - '''Test that the URL label is showing''' - self.assertTrue(mode.server_status.url_description.isVisible()) - - def test_have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - self.assertTrue(mode.server_status.copy_url_button.isVisible()) - - def test_server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) - if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - - def test_web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def test_history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - self.assertFalse(mode.history.empty.isVisible()) - self.assertTrue(mode.history.not_empty.isVisible()) - - def test_counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) - - def test_server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) - - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) - if type(mode) == ShareMode: - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 91013d92..262c7aba 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -12,128 +12,27 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ReceiveModeTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { - "public_mode": False, "receive_allow_receiver_shutdown": True } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=6) - def test_click_mode(self): - CommonTests.test_click_mode(self, self.gui.receive_mode) - - @pytest.mark.run(order=6) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.receive_mode) - - @pytest.mark.run(order=7) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.receive_mode) - - @pytest.mark.run(order=8) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.receive_mode) - - @pytest.mark.run(order=8) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - - @pytest.mark.run(order=9) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode) - - @pytest.mark.run(order=10) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.receive_mode) - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.receive_mode, False) - - @pytest.mark.run(order=15) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, self.gui.receive_mode) - - @pytest.mark.run(order=16) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, self.gui.receive_mode) - - @pytest.mark.run(order=17) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode) - - @pytest.mark.run(order=18) - def test_web_page(self): - CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', False) - - @pytest.mark.run(order=19) - def test_upload_file(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=20) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, self.gui.receive_mode) - - @pytest.mark.run(order=21) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1) - - @pytest.mark.run(order=22) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=23) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2) - - @pytest.mark.run(order=24) - def test_history_indicator(self): - CommonTests.test_history_indicator(self, self.gui.receive_mode, False) - - @pytest.mark.run(order=25) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False) - - @pytest.mark.run(order=26) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=27) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py index 42f237c9..201402c2 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -8,133 +8,32 @@ import json from PyQt5 import QtWidgets from onionshare.common import Common -from onionshare.settings import Settings from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ReceiveModePublicModeTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_click_mode(self): - CommonTests.test_click_mode(self, self.gui.receive_mode) - - @pytest.mark.run(order=6) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.receive_mode) - - @pytest.mark.run(order=7) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.receive_mode) - - @pytest.mark.run(order=8) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.receive_mode) - - @pytest.mark.run(order=9) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - - @pytest.mark.run(order=10) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.receive_mode) - - @pytest.mark.run(order=11) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=12) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.receive_mode) - - @pytest.mark.run(order=13) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.receive_mode, True) - - @pytest.mark.run(order=15) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, self.gui.receive_mode) - - @pytest.mark.run(order=16) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, self.gui.receive_mode) - - @pytest.mark.run(order=17) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.receive_mode) - - @pytest.mark.run(order=18) - def test_web_page(self): - CommonTests.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', True) - - @pytest.mark.run(order=19) - def test_upload_file(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=20) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, self.gui.receive_mode) - - @pytest.mark.run(order=21) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, self.gui.receive_mode, 1) - - @pytest.mark.run(order=22) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=23) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, self.gui.receive_mode, 2) - - @pytest.mark.run(order=24) - def test_history_indicator(self): - CommonTests.test_history_indicator(self, self.gui.receive_mode, True) - - @pytest.mark.run(order=25) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.receive_mode, False) - - @pytest.mark.run(order=26) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=27) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, True, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 9caad8b1..b24a3a78 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -12,139 +12,26 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ShareModeTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { - "public_mode": False, - "close_after_first_download": True } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.share_mode) - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.share_mode, False) - - @pytest.mark.run(order=20) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, self.gui.share_mode) - - @pytest.mark.run(order=21) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, self.gui.share_mode) - - @pytest.mark.run(order=22) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) - - @pytest.mark.run(order=23) - def test_web_page(self): - CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', False) - - @pytest.mark.run(order=24) - def test_download_share(self): - CommonTests.test_download_share(self, False) - - @pytest.mark.run(order=25) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, self.gui.share_mode) - - @pytest.mark.run(order=26) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.share_mode, False) - - @pytest.mark.run(order=27) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=28) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False) - - @pytest.mark.run(order=29) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - @pytest.mark.run(order=30) - def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - CommonTests.test_history_indicator(self, self.gui.share_mode, False) - + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py index c7b05543..e59b53f4 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -12,138 +12,27 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ShareModePublicModeTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { - "public_mode": True + "public_mode": True, } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.share_mode) - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.share_mode, True) - - @pytest.mark.run(order=20) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, self.gui.share_mode) - - @pytest.mark.run(order=21) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, self.gui.share_mode) - - @pytest.mark.run(order=22) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) - - @pytest.mark.run(order=23) - def test_web_page(self): - CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True) - - @pytest.mark.run(order=24) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=25) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, self.gui.share_mode) - - @pytest.mark.run(order=26) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.share_mode, False) - - @pytest.mark.run(order=27) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=28) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, False) - - @pytest.mark.run(order=29) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - @pytest.mark.run(order=30) - def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - CommonTests.test_history_indicator(self, self.gui.share_mode, True) - + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, True, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py index 478177c0..9394c34a 100644 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -12,151 +12,27 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ShareModeStayOpenTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { - "public_mode": True, - "close_after_first_download": False + "close_after_first_download": False, } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.share_mode) - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.share_mode, True) - - @pytest.mark.run(order=20) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, self.gui.share_mode) - - @pytest.mark.run(order=21) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, self.gui.share_mode) - - @pytest.mark.run(order=22) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) - - @pytest.mark.run(order=23) - def test_web_page(self): - CommonTests.test_web_page(self, self.gui.share_mode, 'Total size', True) - - @pytest.mark.run(order=24) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=25) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, self.gui.share_mode) - - @pytest.mark.run(order=26) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, self.gui.share_mode, 1) - - @pytest.mark.run(order=27) - def test_download_share_again(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=28) - def test_counter_incremented_again(self): - CommonTests.test_counter_incremented(self, self.gui.share_mode, 2) - - @pytest.mark.run(order=29) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) - - @pytest.mark.run(order=30) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=31) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True) - - @pytest.mark.run(order=32) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - @pytest.mark.run(order=33) - def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - CommonTests.test_history_indicator(self, self.gui.share_mode, True) - + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py index f4139afb..ab845f8e 100644 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -12,114 +12,37 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ShareModePersistentSlugTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { "public_mode": False, "slug": "", - "save_private_key": True + "save_private_key": True, + "close_after_first_download": False, } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, self.gui.share_mode) - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=10) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - - @pytest.mark.run(order=11) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - - @pytest.mark.run(order=12) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=13) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=14) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=15) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, self.gui.share_mode, False) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) global slug slug = self.gui.share_mode.server_status.web.slug - @pytest.mark.run(order=16) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, self.gui.share_mode) - - @pytest.mark.run(order=17) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) - - @pytest.mark.run(order=18) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=19) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, self.gui.share_mode, True) - - @pytest.mark.run(order=20) - def test_server_started_again(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=21) + @pytest.mark.run(order=3) def test_have_same_slug(self): '''Test that we have the same slug''' self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - @pytest.mark.run(order=22) - def test_server_is_stopped_again(self): - CommonTests.test_server_is_stopped(self, self.gui.share_mode, True) - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=23) - def test_history_indicator(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - CommonTests.test_history_indicator(self, self.gui.share_mode, False) - - if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py index ef55886e..60c616cc 100644 --- a/tests_gui_local/onionshare_timer_test.py +++ b/tests_gui_local/onionshare_timer_test.py @@ -12,76 +12,28 @@ from onionshare.web import Web from onionshare import onion, strings from onionshare_gui import * -from .commontests import CommonTests +from .GuiBaseTest import GuiBaseTest -class OnionShareGuiTest(unittest.TestCase): +class ShareModeTimerTest(unittest.TestCase): @classmethod def setUpClass(cls): test_settings = { "public_mode": False, - "shutdown_timeout": True + "shutdown_timeout": True, } - cls.gui = CommonTests.set_up(test_settings) + cls.gui = GuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): - CommonTests.tear_down() + GuiBaseTest.tear_down() @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, self.gui.share_mode) - - @pytest.mark.run(order=8) - def test_set_timeout(self): - CommonTests.test_set_timeout(self, self.gui.share_mode, 5) - - @pytest.mark.run(order=9) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - - @pytest.mark.run(order=10) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, self.gui.share_mode) - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, self.gui.share_mode) - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=13) - def test_timeout_widget_hidden(self): - CommonTests.test_timeout_widget_hidden(self, self.gui.share_mode) - - @pytest.mark.run(order=14) - def test_timeout(self): - CommonTests.test_server_timed_out(self, self.gui.share_mode, 10000) - - @pytest.mark.run(order=15) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) + def test_run_all_share_mode_timer_tests(self): + GuiBaseTest.run_all_share_mode_timer_tests(self, False) if __name__ == "__main__": unittest.main() -- cgit v1.2.3-54-g00ecf From 43eda6b9df808b2ef29b98ae3b9fb9803843cfc2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 11 Oct 2018 16:04:37 +1100 Subject: Try and move local tests into main tests dir. Rename local tests. Save test settings to unique json files to avoid race conditions --- .travis.yml | 3 +- tests/GuiBaseTest.py | 427 ++++++++++++++++++++ tests/__init__.py | 1 + tests/local_receive_mode_public_mode_test.py | 39 ++ tests/local_receive_mode_test.py | 38 ++ tests/local_share_mode_persistent_slug_test.py | 48 +++ tests/local_share_mode_public_mode_test.py | 38 ++ tests/local_share_mode_stay_open_test.py | 38 ++ tests/local_share_mode_test.py | 37 ++ tests/local_share_mode_timer_test.py | 39 ++ tests_gui_local/GuiBaseTest.py | 428 --------------------- tests_gui_local/__init__.py | 1 - tests_gui_local/conftest.py | 160 -------- .../onionshare_receive_mode_upload_test.py | 38 -- ...onshare_receive_mode_upload_test_public_mode.py | 39 -- .../onionshare_share_mode_download_test.py | 37 -- ...onshare_share_mode_download_test_public_mode.py | 38 -- ...nionshare_share_mode_download_test_stay_open.py | 38 -- tests_gui_local/onionshare_slug_persistent_test.py | 48 --- tests_gui_local/onionshare_timer_test.py | 39 -- tests_gui_local/run_unit_tests.sh | 5 - 21 files changed, 706 insertions(+), 873 deletions(-) create mode 100644 tests/GuiBaseTest.py create mode 100644 tests/local_receive_mode_public_mode_test.py create mode 100644 tests/local_receive_mode_test.py create mode 100644 tests/local_share_mode_persistent_slug_test.py create mode 100644 tests/local_share_mode_public_mode_test.py create mode 100644 tests/local_share_mode_stay_open_test.py create mode 100644 tests/local_share_mode_test.py create mode 100644 tests/local_share_mode_timer_test.py delete mode 100644 tests_gui_local/GuiBaseTest.py delete mode 100644 tests_gui_local/__init__.py delete mode 100644 tests_gui_local/conftest.py delete mode 100644 tests_gui_local/onionshare_receive_mode_upload_test.py delete mode 100644 tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test_public_mode.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test_stay_open.py delete mode 100644 tests_gui_local/onionshare_slug_persistent_test.py delete mode 100644 tests_gui_local/onionshare_timer_test.py delete mode 100755 tests_gui_local/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index e0b5b822..24255416 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,4 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # run CLI tests and local GUI tests script: - - pytest --cov=onionshare tests/ - - cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh + - xvfb-run pytest --cov=onionshare tests/ diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py new file mode 100644 index 00000000..7aec97f7 --- /dev/null +++ b/tests/GuiBaseTest.py @@ -0,0 +1,427 @@ +import os +import requests +import socket +import socks +import zipfile +import json +import shutil + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + + +class GuiBaseTest(object): + @staticmethod + def set_up(test_settings, settings_filename='/tmp/testsettings.json'): + '''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) + 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): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if type(mode) == ReceiveMode: + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if type(mode) == ShareMode: + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) + + def test_history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + def test_history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + self.assertFalse(mode.history.isVisible()) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible''' + self.assertTrue(mode.history.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + self.assertTrue(mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + self.assertEquals(mode.history.completed_count, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if type(mode) == ReceiveMode: + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + if type(mode) == ShareMode: + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + # Auto-stop timer tests + def test_set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest + + def run_all_common_setup_tests(self): + GuiBaseTest.test_gui_loaded(self) + GuiBaseTest.test_windowTitle_seen(self) + GuiBaseTest.test_settings_button_is_visible(self) + GuiBaseTest.test_server_status_bar_is_visible(self) + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + GuiBaseTest.test_click_mode(self, self.gui.share_mode) + GuiBaseTest.test_file_selection_widget_has_a_file(self) + GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) + GuiBaseTest.test_deleting_only_file_hides_delete_button(self) + GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) + GuiBaseTest.test_file_selection_widget_readd_files(self) + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) + GuiBaseTest.test_add_delete_buttons_hidden(self) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) + GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + GuiBaseTest.test_download_share(self, public_mode) + GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_add_button_visible(self) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) + GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) + GuiBaseTest.test_web_service_is_stopped(self) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + GuiBaseTest.test_click_mode(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) + GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') + GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..1def7f5e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +from .GuiBaseTest import GuiBaseTest diff --git a/tests/local_receive_mode_public_mode_test.py b/tests/local_receive_mode_public_mode_test.py new file mode 100644 index 00000000..0bc00833 --- /dev/null +++ b/tests/local_receive_mode_public_mode_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ReceiveModePublicModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ReceiveModePublicModeTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_receive_mode_test.py b/tests/local_receive_mode_test.py new file mode 100644 index 00000000..82d7529a --- /dev/null +++ b/tests/local_receive_mode_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ReceiveModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ReceiveModeTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_share_mode_persistent_slug_test.py b/tests/local_share_mode_persistent_slug_test.py new file mode 100644 index 00000000..cad01ed9 --- /dev/null +++ b/tests/local_share_mode_persistent_slug_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModePersistentSlugTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModePersistentSlugTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1000) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=1001) + def test_run_all_persistent_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=1002) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_share_mode_public_mode_test.py b/tests/local_share_mode_public_mode_test.py new file mode 100644 index 00000000..2aa3caea --- /dev/null +++ b/tests/local_share_mode_public_mode_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModePublicModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModePublicModeTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_share_mode_stay_open_test.py b/tests/local_share_mode_stay_open_test.py new file mode 100644 index 00000000..0a2db984 --- /dev/null +++ b/tests/local_share_mode_stay_open_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeStayOpenTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeStayOpenTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_share_mode_test.py b/tests/local_share_mode_test.py new file mode 100644 index 00000000..ca1bed2c --- /dev/null +++ b/tests/local_share_mode_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_share_mode_timer_test.py b/tests/local_share_mode_timer_test.py new file mode 100644 index 00000000..ffa138a6 --- /dev/null +++ b/tests/local_share_mode_timer_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeTimerTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeTimerTest.json') + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_timer_tests(self): + GuiBaseTest.run_all_share_mode_timer_tests(self, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py deleted file mode 100644 index e7d25031..00000000 --- a/tests_gui_local/GuiBaseTest.py +++ /dev/null @@ -1,428 +0,0 @@ -import os -import requests -import socket -import socks -import zipfile -import json -import shutil - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - - -class GuiBaseTest(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): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - def test_click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if type(mode) == ReceiveMode: - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if type(mode) == ShareMode: - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - def test_click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' - currently_visible = mode.history.isVisible() - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertEqual(mode.history.isVisible(), not currently_visible) - - def test_history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) - else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) - r = requests.get(url) - QtTest.QTest.qWait(2000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - def test_history_is_not_visible(self, mode): - '''Test that the History section is not visible''' - self.assertFalse(mode.history.isVisible()) - - def test_history_is_visible(self, mode): - '''Test that the History section is visible''' - self.assertTrue(mode.history.isVisible()) - - def test_server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 1) - - def test_server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - def test_a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - - def test_url_description_shown(self, mode): - '''Test that the URL label is showing''' - self.assertTrue(mode.server_status.url_description.isVisible()) - - def test_have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - self.assertTrue(mode.server_status.copy_url_button.isVisible()) - - def test_server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) - if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - - def test_web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def test_history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - self.assertFalse(mode.history.empty.isVisible()) - self.assertTrue(mode.history.not_empty.isVisible()) - - def test_counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) - - def test_server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) - - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) - if type(mode) == ShareMode: - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - - # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest - - def run_all_common_setup_tests(self): - GuiBaseTest.test_gui_loaded(self) - GuiBaseTest.test_windowTitle_seen(self) - GuiBaseTest.test_settings_button_is_visible(self) - GuiBaseTest.test_server_status_bar_is_visible(self) - - def run_all_share_mode_setup_tests(self): - """Tests in share mode prior to starting a share""" - GuiBaseTest.test_click_mode(self, self.gui.share_mode) - GuiBaseTest.test_file_selection_widget_has_a_file(self) - GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) - GuiBaseTest.test_deleting_only_file_hides_delete_button(self) - GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) - GuiBaseTest.test_file_selection_widget_readd_files(self) - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) - GuiBaseTest.test_add_delete_buttons_hidden(self) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) - GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) - - def run_all_share_mode_download_tests(self, public_mode, stay_open): - """Tests in share mode after downloading a share""" - GuiBaseTest.test_download_share(self, public_mode) - GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_add_button_visible(self) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) - - def run_all_share_mode_tests(self, public_mode, stay_open): - """End-to-end share tests""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) - GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) - GuiBaseTest.test_web_service_is_stopped(self) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - GuiBaseTest.test_click_mode(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) - GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') - GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py deleted file mode 100644 index 7cf168eb..00000000 --- a/tests_gui_local/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .GuiBaseTest import GuiBaseTest diff --git a/tests_gui_local/conftest.py b/tests_gui_local/conftest.py deleted file mode 100644 index 8ac7efb8..00000000 --- a/tests_gui_local/conftest.py +++ /dev/null @@ -1,160 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - return common.Common() - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py deleted file mode 100644 index 262c7aba..00000000 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ReceiveModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py deleted file mode 100644 index 201402c2..00000000 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ReceiveModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py deleted file mode 100644 index b24a3a78..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py deleted file mode 100644 index e59b53f4..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py deleted file mode 100644 index 9394c34a..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeStayOpenTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py deleted file mode 100644 index ab845f8e..00000000 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePersistentSlugTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=3) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py deleted file mode 100644 index 60c616cc..00000000 --- a/tests_gui_local/onionshare_timer_test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeTimerTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_timer_tests(self): - GuiBaseTest.run_all_share_mode_timer_tests(self, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/run_unit_tests.sh b/tests_gui_local/run_unit_tests.sh deleted file mode 100755 index 7d207a57..00000000 --- a/tests_gui_local/run_unit_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^onionshare_`; do - pytest $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From 418252f7c62d0f3680de292b35dae5ebdb8096a5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 11 Oct 2018 16:07:16 +1100 Subject: Revert "Try and move local tests into main tests dir. Rename local tests. Save test settings to unique json files to avoid race conditions" This reverts commit 43eda6b9df808b2ef29b98ae3b9fb9803843cfc2. --- .travis.yml | 3 +- tests/GuiBaseTest.py | 427 -------------------- tests/__init__.py | 1 - tests/local_receive_mode_public_mode_test.py | 39 -- tests/local_receive_mode_test.py | 38 -- tests/local_share_mode_persistent_slug_test.py | 48 --- tests/local_share_mode_public_mode_test.py | 38 -- tests/local_share_mode_stay_open_test.py | 38 -- tests/local_share_mode_test.py | 37 -- tests/local_share_mode_timer_test.py | 39 -- tests_gui_local/GuiBaseTest.py | 428 +++++++++++++++++++++ tests_gui_local/__init__.py | 1 + tests_gui_local/conftest.py | 160 ++++++++ .../onionshare_receive_mode_upload_test.py | 38 ++ ...onshare_receive_mode_upload_test_public_mode.py | 39 ++ .../onionshare_share_mode_download_test.py | 37 ++ ...onshare_share_mode_download_test_public_mode.py | 38 ++ ...nionshare_share_mode_download_test_stay_open.py | 38 ++ tests_gui_local/onionshare_slug_persistent_test.py | 48 +++ tests_gui_local/onionshare_timer_test.py | 39 ++ tests_gui_local/run_unit_tests.sh | 5 + 21 files changed, 873 insertions(+), 706 deletions(-) delete mode 100644 tests/GuiBaseTest.py delete mode 100644 tests/local_receive_mode_public_mode_test.py delete mode 100644 tests/local_receive_mode_test.py delete mode 100644 tests/local_share_mode_persistent_slug_test.py delete mode 100644 tests/local_share_mode_public_mode_test.py delete mode 100644 tests/local_share_mode_stay_open_test.py delete mode 100644 tests/local_share_mode_test.py delete mode 100644 tests/local_share_mode_timer_test.py create mode 100644 tests_gui_local/GuiBaseTest.py create mode 100644 tests_gui_local/__init__.py create mode 100644 tests_gui_local/conftest.py create mode 100644 tests_gui_local/onionshare_receive_mode_upload_test.py create mode 100644 tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test_public_mode.py create mode 100644 tests_gui_local/onionshare_share_mode_download_test_stay_open.py create mode 100644 tests_gui_local/onionshare_slug_persistent_test.py create mode 100644 tests_gui_local/onionshare_timer_test.py create mode 100755 tests_gui_local/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index 24255416..e0b5b822 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,5 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # run CLI tests and local GUI tests script: - - xvfb-run pytest --cov=onionshare tests/ + - pytest --cov=onionshare tests/ + - cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py deleted file mode 100644 index 7aec97f7..00000000 --- a/tests/GuiBaseTest.py +++ /dev/null @@ -1,427 +0,0 @@ -import os -import requests -import socket -import socks -import zipfile -import json -import shutil - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - - -class GuiBaseTest(object): - @staticmethod - def set_up(test_settings, settings_filename='/tmp/testsettings.json'): - '''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) - 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): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - def test_windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - def test_settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - def test_server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - def test_click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if type(mode) == ReceiveMode: - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if type(mode) == ShareMode: - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - def test_click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' - currently_visible = mode.history.isVisible() - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertEqual(mode.history.isVisible(), not currently_visible) - - def test_history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) - else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) - r = requests.get(url) - QtTest.QTest.qWait(2000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - def test_history_is_not_visible(self, mode): - '''Test that the History section is not visible''' - self.assertFalse(mode.history.isVisible()) - - def test_history_is_visible(self, mode): - '''Test that the History section is visible''' - self.assertTrue(mode.history.isVisible()) - - def test_server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 1) - - def test_server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - def test_settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - def test_a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - def test_a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - - def test_url_description_shown(self, mode): - '''Test that the URL label is showing''' - self.assertTrue(mode.server_status.url_description.isVisible()) - - def test_have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - self.assertTrue(mode.server_status.copy_url_button.isVisible()) - - def test_server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) - if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - - def test_web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def test_history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - self.assertFalse(mode.history.empty.isVisible()) - self.assertTrue(mode.history.not_empty.isVisible()) - - def test_counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) - - def test_server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) - - def test_web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - def test_server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) - if type(mode) == ShareMode: - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - - # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest - - def run_all_common_setup_tests(self): - GuiBaseTest.test_gui_loaded(self) - GuiBaseTest.test_windowTitle_seen(self) - GuiBaseTest.test_settings_button_is_visible(self) - GuiBaseTest.test_server_status_bar_is_visible(self) - - def run_all_share_mode_setup_tests(self): - """Tests in share mode prior to starting a share""" - GuiBaseTest.test_click_mode(self, self.gui.share_mode) - GuiBaseTest.test_file_selection_widget_has_a_file(self) - GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) - GuiBaseTest.test_deleting_only_file_hides_delete_button(self) - GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) - GuiBaseTest.test_file_selection_widget_readd_files(self) - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) - GuiBaseTest.test_add_delete_buttons_hidden(self) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) - GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) - - def run_all_share_mode_download_tests(self, public_mode, stay_open): - """Tests in share mode after downloading a share""" - GuiBaseTest.test_download_share(self, public_mode) - GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_add_button_visible(self) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) - - def run_all_share_mode_tests(self, public_mode, stay_open): - """End-to-end share tests""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) - GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) - GuiBaseTest.test_web_service_is_stopped(self) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - GuiBaseTest.test_click_mode(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) - GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') - GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests/__init__.py b/tests/__init__.py index 1def7f5e..e69de29b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -from .GuiBaseTest import GuiBaseTest diff --git a/tests/local_receive_mode_public_mode_test.py b/tests/local_receive_mode_public_mode_test.py deleted file mode 100644 index 0bc00833..00000000 --- a/tests/local_receive_mode_public_mode_test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ReceiveModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ReceiveModePublicModeTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_receive_mode_test.py b/tests/local_receive_mode_test.py deleted file mode 100644 index 82d7529a..00000000 --- a/tests/local_receive_mode_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ReceiveModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ReceiveModeTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_share_mode_persistent_slug_test.py b/tests/local_share_mode_persistent_slug_test.py deleted file mode 100644 index cad01ed9..00000000 --- a/tests/local_share_mode_persistent_slug_test.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePersistentSlugTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModePersistentSlugTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1000) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=1001) - def test_run_all_persistent_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=1002) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_share_mode_public_mode_test.py b/tests/local_share_mode_public_mode_test.py deleted file mode 100644 index 2aa3caea..00000000 --- a/tests/local_share_mode_public_mode_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModePublicModeTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_share_mode_stay_open_test.py b/tests/local_share_mode_stay_open_test.py deleted file mode 100644 index 0a2db984..00000000 --- a/tests/local_share_mode_stay_open_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeStayOpenTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeStayOpenTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_share_mode_test.py b/tests/local_share_mode_test.py deleted file mode 100644 index ca1bed2c..00000000 --- a/tests/local_share_mode_test.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_share_mode_timer_test.py b/tests/local_share_mode_timer_test.py deleted file mode 100644 index ffa138a6..00000000 --- a/tests/local_share_mode_timer_test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeTimerTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = GuiBaseTest.set_up(test_settings, '/tmp/ShareModeTimerTest.json') - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_timer_tests(self): - GuiBaseTest.run_all_share_mode_timer_tests(self, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py new file mode 100644 index 00000000..e7d25031 --- /dev/null +++ b/tests_gui_local/GuiBaseTest.py @@ -0,0 +1,428 @@ +import os +import requests +import socket +import socks +import zipfile +import json +import shutil + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + + +class GuiBaseTest(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): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + def test_click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if type(mode) == ReceiveMode: + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if type(mode) == ShareMode: + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + def test_click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) + + def test_history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + def test_history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + self.assertFalse(mode.history.isVisible()) + + def test_history_is_visible(self, mode): + '''Test that the History section is visible''' + self.assertTrue(mode.history.isVisible()) + + def test_server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) + + def test_server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + def test_a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + + def test_url_description_shown(self, mode): + '''Test that the URL label is showing''' + self.assertTrue(mode.server_status.url_description.isVisible()) + + def test_have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + def test_server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + + def test_web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def test_history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) + + def test_counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + self.assertEquals(mode.history.completed_count, count) + + def test_server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) + + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + def test_server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if type(mode) == ReceiveMode: + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + if type(mode) == ShareMode: + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + # Auto-stop timer tests + def test_set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + def test_timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + def test_server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Receive-specific tests + def test_upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + # Share-specific tests + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def test_add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + def test_download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + def test_add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest + + def run_all_common_setup_tests(self): + GuiBaseTest.test_gui_loaded(self) + GuiBaseTest.test_windowTitle_seen(self) + GuiBaseTest.test_settings_button_is_visible(self) + GuiBaseTest.test_server_status_bar_is_visible(self) + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + GuiBaseTest.test_click_mode(self, self.gui.share_mode) + GuiBaseTest.test_file_selection_widget_has_a_file(self) + GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) + GuiBaseTest.test_deleting_only_file_hides_delete_button(self) + GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) + GuiBaseTest.test_file_selection_widget_readd_files(self) + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) + GuiBaseTest.test_add_delete_buttons_hidden(self) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) + GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + GuiBaseTest.test_download_share(self, public_mode) + GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) + GuiBaseTest.test_add_button_visible(self) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) + GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + GuiBaseTest.run_all_share_mode_setup_tests(self) + GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) + GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) + GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) + GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) + GuiBaseTest.test_web_service_is_stopped(self) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + GuiBaseTest.test_click_mode(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) + GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) + GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) + GuiBaseTest.test_settings_button_is_hidden(self) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_a_web_server_is_running(self) + GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) + GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) + GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) + GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') + GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) + GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') + GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) + GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) + GuiBaseTest.test_web_service_is_stopped(self) + GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) + GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) + GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) + GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py new file mode 100644 index 00000000..7cf168eb --- /dev/null +++ b/tests_gui_local/__init__.py @@ -0,0 +1 @@ +from .GuiBaseTest import GuiBaseTest diff --git a/tests_gui_local/conftest.py b/tests_gui_local/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/tests_gui_local/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..262c7aba --- /dev/null +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ReceiveModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py new file mode 100644 index 00000000..201402c2 --- /dev/null +++ b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ReceiveModePublicModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_receive_mode_tests(self, True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..b24a3a78 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py new file mode 100644 index 00000000..e59b53f4 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModePublicModeTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py new file mode 100644 index 00000000..9394c34a --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeStayOpenTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py new file mode 100644 index 00000000..ab845f8e --- /dev/null +++ b/tests_gui_local/onionshare_slug_persistent_test.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModePersistentSlugTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiBaseTest.run_all_share_mode_tests(self, False, True) + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=3) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py new file mode 100644 index 00000000..60c616cc --- /dev/null +++ b/tests_gui_local/onionshare_timer_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import pytest +import json + +from PyQt5 import QtWidgets + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +from .GuiBaseTest import GuiBaseTest + +class ShareModeTimerTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiBaseTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiBaseTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiBaseTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_timer_tests(self): + GuiBaseTest.run_all_share_mode_timer_tests(self, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/run_unit_tests.sh b/tests_gui_local/run_unit_tests.sh new file mode 100755 index 00000000..7d207a57 --- /dev/null +++ b/tests_gui_local/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + pytest $test -vvv || exit 1 +done -- cgit v1.2.3-54-g00ecf From fe091db5960db2afa33533e54f2addb660045c3f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 11:28:47 +1100 Subject: Refactor tests to use proper inheritance of GuiReceiveTest/GuiShareTest (which inherit from GuiBaseTest). Prevent tests from auto-firing in these base objects. Clean up imported modules, rename files to end in _test.py --- tests_gui_local/GuiBaseTest.py | 262 +++++---------------- tests_gui_local/GuiReceiveTest.py | 45 ++++ tests_gui_local/GuiShareTest.py | 154 ++++++++++++ tests_gui_local/__init__.py | 1 - ...onshare_receive_mode_upload_public_mode_test.py | 29 +++ .../onionshare_receive_mode_upload_test.py | 26 +- ...onshare_receive_mode_upload_test_public_mode.py | 39 --- ...onshare_share_mode_download_public_mode_test.py | 28 +++ ...nionshare_share_mode_download_stay_open_test.py | 28 +++ .../onionshare_share_mode_download_test.py | 24 +- ...onshare_share_mode_download_test_public_mode.py | 38 --- ...nionshare_share_mode_download_test_stay_open.py | 38 --- .../onionshare_share_mode_slug_persistent_test.py | 38 +++ .../onionshare_share_mode_timer_test.py | 29 +++ tests_gui_local/onionshare_slug_persistent_test.py | 48 ---- tests_gui_local/onionshare_timer_test.py | 39 --- 16 files changed, 421 insertions(+), 445 deletions(-) create mode 100644 tests_gui_local/GuiReceiveTest.py create mode 100644 tests_gui_local/GuiShareTest.py create mode 100644 tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py delete mode 100644 tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 tests_gui_local/onionshare_share_mode_download_public_mode_test.py create mode 100644 tests_gui_local/onionshare_share_mode_download_stay_open_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test_public_mode.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test_stay_open.py create mode 100644 tests_gui_local/onionshare_share_mode_slug_persistent_test.py create mode 100644 tests_gui_local/onionshare_share_mode_timer_test.py delete mode 100644 tests_gui_local/onionshare_slug_persistent_test.py delete mode 100644 tests_gui_local/onionshare_timer_test.py diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py index e7d25031..8a8b127e 100644 --- a/tests_gui_local/GuiBaseTest.py +++ b/tests_gui_local/GuiBaseTest.py @@ -1,10 +1,9 @@ +import json import os import requests +import shutil import socket import socks -import zipfile -import json -import shutil from PyQt5 import QtCore, QtTest @@ -59,24 +58,28 @@ class GuiBaseTest(object): except: pass - - def test_gui_loaded(self): + + def gui_loaded(self): '''Test that the GUI actually is shown''' self.assertTrue(self.gui.show) - def test_windowTitle_seen(self): + + def windowTitle_seen(self): '''Test that the window title is OnionShare''' self.assertEqual(self.gui.windowTitle(), 'OnionShare') - def test_settings_button_is_visible(self): + + def settings_button_is_visible(self): '''Test that the settings button is visible''' self.assertTrue(self.gui.settings_button.isVisible()) - def test_server_status_bar_is_visible(self): + + def server_status_bar_is_visible(self): '''Test that the status bar is visible''' self.assertTrue(self.gui.status_bar.isVisible()) - def test_click_mode(self, mode): + + def click_mode(self, mode): '''Test that we can switch Mode by clicking the button''' if type(mode) == ReceiveMode: QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) @@ -85,13 +88,15 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - def test_click_toggle_history(self, mode): + + def click_toggle_history(self, mode): '''Test that we can toggle Download or Upload history by clicking the toggle button''' currently_visible = mode.history.isVisible() QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertEqual(mode.history.isVisible(), not currently_visible) - def test_history_indicator(self, mode, public_mode): + + def history_indicator(self, mode, public_mode): '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' # Make sure history is toggled off if mode.history.isVisible(): @@ -128,63 +133,75 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - def test_history_is_not_visible(self, mode): + + def history_is_not_visible(self, mode): '''Test that the History section is not visible''' self.assertFalse(mode.history.isVisible()) - def test_history_is_visible(self, mode): + + def history_is_visible(self, mode): '''Test that the History section is visible''' self.assertTrue(mode.history.isVisible()) - def test_server_working_on_start_button_pressed(self, mode): + + def server_working_on_start_button_pressed(self, mode): '''Test we can start the service''' # Should be in SERVER_WORKING state QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 1) - def test_server_status_indicator_says_starting(self, mode): + + def server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - def test_settings_button_is_hidden(self): + + def settings_button_is_hidden(self): '''Test that the settings button is hidden when the server starts''' self.assertFalse(self.gui.settings_button.isVisible()) - def test_a_server_is_started(self, mode): + + def a_server_is_started(self, mode): '''Test that the server has started''' QtTest.QTest.qWait(2000) # Should now be in SERVER_STARTED state self.assertEqual(mode.server_status.status, 2) - def test_a_web_server_is_running(self): + + def a_web_server_is_running(self): '''Test that the web server has started''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - def test_have_a_slug(self, mode, public_mode): + + def have_a_slug(self, mode, public_mode): '''Test that we have a valid slug''' if not public_mode: self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') else: self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - def test_url_description_shown(self, mode): + + def url_description_shown(self, mode): '''Test that the URL label is showing''' self.assertTrue(mode.server_status.url_description.isVisible()) - def test_have_copy_url_button(self, mode): + + def have_copy_url_button(self, mode): '''Test that the Copy URL button is shown''' self.assertTrue(mode.server_status.copy_url_button.isVisible()) - def test_server_status_indicator_says_started(self, mode): + + def server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if type(mode) == ReceiveMode: self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) if type(mode) == ShareMode: self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - def test_web_page(self, mode, string, public_mode): + + def web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' s = socks.socksocket() s.settimeout(60) @@ -212,22 +229,26 @@ class GuiBaseTest(object): self.assertTrue(string in f.read()) f.close() - def test_history_widgets_present(self, mode): + + def history_widgets_present(self, mode): '''Test that the relevant widgets are present in the history view after activity has taken place''' self.assertFalse(mode.history.empty.isVisible()) self.assertTrue(mode.history.not_empty.isVisible()) - def test_counter_incremented(self, mode, count): + + def counter_incremented(self, mode, count): '''Test that the counter has incremented''' self.assertEquals(mode.history.completed_count, count) - def test_server_is_stopped(self, mode, stay_open): + + def server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEquals(mode.server_status.status, 0) - def test_web_service_is_stopped(self): + + def web_service_is_stopped(self): '''Test that the web server also stopped''' QtTest.QTest.qWait(2000) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -235,7 +256,8 @@ class GuiBaseTest(object): # We should be closed by now. Fail if not! self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - def test_server_status_indicator_says_closed(self, mode, stay_open): + + def server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if type(mode) == ReceiveMode: self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) @@ -245,184 +267,10 @@ class GuiBaseTest(object): else: self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - # Auto-stop timer tests - def test_set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - def test_timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - def test_server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Receive-specific tests - def test_upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - # Share-specific tests - def test_file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - def test_deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - def test_file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - def test_add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - def test_download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - def test_add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - + def run_all_common_setup_tests(self): + self.gui_loaded() + self.windowTitle_seen() + self.settings_button_is_visible() + self.server_status_bar_is_visible() - # The following are 'groupings' of tests used by other objects that inherit GuiBaseTest - def run_all_common_setup_tests(self): - GuiBaseTest.test_gui_loaded(self) - GuiBaseTest.test_windowTitle_seen(self) - GuiBaseTest.test_settings_button_is_visible(self) - GuiBaseTest.test_server_status_bar_is_visible(self) - - def run_all_share_mode_setup_tests(self): - """Tests in share mode prior to starting a share""" - GuiBaseTest.test_click_mode(self, self.gui.share_mode) - GuiBaseTest.test_file_selection_widget_has_a_file(self) - GuiBaseTest.test_history_is_not_visible(self, self.gui.share_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.share_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.share_mode) - GuiBaseTest.test_deleting_only_file_hides_delete_button(self) - GuiBaseTest.test_add_a_file_and_delete_using_its_delete_widget(self) - GuiBaseTest.test_file_selection_widget_readd_files(self) - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.share_mode) - GuiBaseTest.test_add_delete_buttons_hidden(self) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.share_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.share_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.share_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.share_mode) - GuiBaseTest.test_web_page(self, self.gui.share_mode, 'Total size', public_mode) - - def run_all_share_mode_download_tests(self, public_mode, stay_open): - """Tests in share mode after downloading a share""" - GuiBaseTest.test_download_share(self, public_mode) - GuiBaseTest.test_history_widgets_present(self, self.gui.share_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.share_mode, stay_open) - GuiBaseTest.test_add_button_visible(self) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.share_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.share_mode) - GuiBaseTest.test_history_indicator(self, self.gui.share_mode, public_mode) - - def run_all_share_mode_tests(self, public_mode, stay_open): - """End-to-end share tests""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.run_all_share_mode_download_tests(self, public_mode, stay_open) - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - GuiBaseTest.run_all_share_mode_setup_tests(self) - GuiBaseTest.test_set_timeout(self, self.gui.share_mode, 5) - GuiBaseTest.run_all_share_mode_started_tests(self, public_mode) - GuiBaseTest.test_timeout_widget_hidden(self, self.gui.share_mode) - GuiBaseTest.test_server_timed_out(self, self.gui.share_mode, 10000) - GuiBaseTest.test_web_service_is_stopped(self) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - GuiBaseTest.test_click_mode(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_not_visible(self, self.gui.receive_mode) - GuiBaseTest.test_click_toggle_history(self, self.gui.receive_mode) - GuiBaseTest.test_history_is_visible(self, self.gui.receive_mode) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_starting(self, self.gui.receive_mode) - GuiBaseTest.test_settings_button_is_hidden(self) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_a_web_server_is_running(self) - GuiBaseTest.test_have_a_slug(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_url_description_shown(self, self.gui.receive_mode) - GuiBaseTest.test_have_copy_url_button(self, self.gui.receive_mode) - GuiBaseTest.test_server_status_indicator_says_started(self, self.gui.receive_mode) - GuiBaseTest.test_web_page(self, self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test.txt') - GuiBaseTest.test_history_widgets_present(self, self.gui.receive_mode) - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 1) - GuiBaseTest.test_upload_file(self, public_mode, '/tmp/OnionShare/test-2.txt') - GuiBaseTest.test_counter_incremented(self, self.gui.receive_mode, 2) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) - GuiBaseTest.test_server_is_stopped(self, self.gui.receive_mode, False) - GuiBaseTest.test_web_service_is_stopped(self) - GuiBaseTest.test_server_status_indicator_says_closed(self, self.gui.receive_mode, False) - GuiBaseTest.test_server_working_on_start_button_pressed(self, self.gui.receive_mode) - GuiBaseTest.test_a_server_is_started(self, self.gui.receive_mode) - GuiBaseTest.test_history_indicator(self, self.gui.receive_mode, public_mode) diff --git a/tests_gui_local/GuiReceiveTest.py b/tests_gui_local/GuiReceiveTest.py new file mode 100644 index 00000000..1fa5c4dc --- /dev/null +++ b/tests_gui_local/GuiReceiveTest.py @@ -0,0 +1,45 @@ +import os +import requests +from PyQt5 import QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiReceiveTest(GuiBaseTest): + def upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + self.click_mode(self.gui.receive_mode) + self.history_is_not_visible(self.gui.receive_mode) + self.click_toggle_history(self.gui.receive_mode) + self.history_is_visible(self.gui.receive_mode) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.server_status_indicator_says_starting(self.gui.receive_mode) + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.receive_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.receive_mode, public_mode) + self.url_description_shown(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode) + self.server_status_indicator_says_started(self.gui.receive_mode) + self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.counter_incremented(self.gui.receive_mode, 1) + self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.counter_incremented(self.gui.receive_mode, 2) + self.history_indicator(self.gui.receive_mode, public_mode) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.a_server_is_started(self.gui.receive_mode) + self.history_indicator(self.gui.receive_mode, public_mode) + diff --git a/tests_gui_local/GuiShareTest.py b/tests_gui_local/GuiShareTest.py new file mode 100644 index 00000000..8c3ea2b1 --- /dev/null +++ b/tests_gui_local/GuiShareTest.py @@ -0,0 +1,154 @@ +import socks +import zipfile +from PyQt5 import QtCore, QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiShareTest(GuiBaseTest): + # Auto-stop timer tests + + def set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + + def timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + + def server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Share-specific tests + + def file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + + def deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + + def file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + + def add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + + def add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + self.click_mode(self.gui.share_mode) + self.file_selection_widget_has_a_file() + self.history_is_not_visible(self.gui.share_mode) + self.click_toggle_history(self.gui.share_mode) + self.history_is_visible(self.gui.share_mode) + self.deleting_only_file_hides_delete_button() + self.add_a_file_and_delete_using_its_delete_widget() + self.file_selection_widget_readd_files() + + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.share_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.share_mode, public_mode) + self.url_description_shown(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode) + self.server_status_indicator_says_started(self.gui.share_mode) + self.web_page(self.gui.share_mode, 'Total size', public_mode) + + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + self.download_share(public_mode) + self.history_widgets_present(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + self.add_button_visible() + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.a_server_is_started(self.gui.share_mode) + self.history_indicator(self.gui.share_mode, public_mode) + + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + self.run_all_share_mode_download_tests(public_mode, stay_open) + + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_timeout(self.gui.share_mode, 5) + self.run_all_share_mode_started_tests(public_mode) + self.timeout_widget_hidden(self.gui.share_mode) + self.server_timed_out(self.gui.share_mode, 10000) + self.web_service_is_stopped() + diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py index 7cf168eb..e69de29b 100644 --- a/tests_gui_local/__init__.py +++ b/tests_gui_local/__init__.py @@ -1 +0,0 @@ -from .GuiBaseTest import GuiBaseTest diff --git a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py new file mode 100644 index 00000000..f41a2ce2 --- /dev/null +++ b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class ReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiReceiveTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_receive_mode_tests(self): + GuiReceiveTest.run_all_receive_mode_tests(self, True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 262c7aba..8a7661dd 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -1,38 +1,28 @@ #!/usr/bin/env python3 -import os -import sys -import unittest import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * +import unittest -from .GuiBaseTest import GuiBaseTest +from .GuiReceiveTest import GuiReceiveTest -class ReceiveModeTest(unittest.TestCase): +class ReceiveModeTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiBaseTest.set_up(test_settings) + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): - GuiBaseTest.tear_down() + GuiReceiveTest.tear_down() @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) + GuiReceiveTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, False, True) + def test_run_all_receive_mode_tests(self): + GuiReceiveTest.run_all_receive_mode_tests(self, False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py deleted file mode 100644 index 201402c2..00000000 --- a/tests_gui_local/onionshare_receive_mode_upload_test_public_mode.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ReceiveModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_receive_mode_tests(self, True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py new file mode 100644 index 00000000..53d1fb8c --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class ShareModePublicModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiShareTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiShareTest.run_all_share_mode_tests(self, True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py new file mode 100644 index 00000000..b398f654 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class ShareModeStayOpenTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiShareTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiShareTest.run_all_share_mode_tests(self, False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index b24a3a78..34532c59 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -1,37 +1,27 @@ #!/usr/bin/env python3 -import os -import sys -import unittest import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * +import unittest -from .GuiBaseTest import GuiBaseTest +from .GuiShareTest import GuiShareTest -class ShareModeTest(unittest.TestCase): +class ShareModeTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): test_settings = { } - cls.gui = GuiBaseTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): - GuiBaseTest.tear_down() + GuiShareTest.tear_down() @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) + GuiShareTest.run_all_common_setup_tests(self) @pytest.mark.run(order=2) def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, False) + GuiShareTest.run_all_share_mode_tests(self, False, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py b/tests_gui_local/onionshare_share_mode_download_test_public_mode.py deleted file mode 100644 index e59b53f4..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test_public_mode.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePublicModeTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py b/tests_gui_local/onionshare_share_mode_download_test_stay_open.py deleted file mode 100644 index 9394c34a..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test_stay_open.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeStayOpenTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py new file mode 100644 index 00000000..b4c92f36 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class ShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiShareTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + GuiShareTest.run_all_share_mode_tests(self, False, True) + global slug + slug = self.gui.share_mode.server_status.web.slug + + @pytest.mark.run(order=3) + def test_have_same_slug(self): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_timer_test.py b/tests_gui_local/onionshare_share_mode_timer_test.py new file mode 100644 index 00000000..98902428 --- /dev/null +++ b/tests_gui_local/onionshare_share_mode_timer_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class ShareModeTimerTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + GuiShareTest.run_all_common_setup_tests(self) + + @pytest.mark.run(order=2) + def test_run_all_share_mode_timer_tests(self): + GuiShareTest.run_all_share_mode_timer_tests(self, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/onionshare_slug_persistent_test.py b/tests_gui_local/onionshare_slug_persistent_test.py deleted file mode 100644 index ab845f8e..00000000 --- a/tests_gui_local/onionshare_slug_persistent_test.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModePersistentSlugTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - GuiBaseTest.run_all_share_mode_tests(self, False, True) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=3) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_timer_test.py b/tests_gui_local/onionshare_timer_test.py deleted file mode 100644 index 60c616cc..00000000 --- a/tests_gui_local/onionshare_timer_test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .GuiBaseTest import GuiBaseTest - -class ShareModeTimerTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = GuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiBaseTest.tear_down() - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - GuiBaseTest.run_all_common_setup_tests(self) - - @pytest.mark.run(order=2) - def test_run_all_share_mode_timer_tests(self): - GuiBaseTest.run_all_share_mode_timer_tests(self, False) - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From 6227c5879683420c185ffb114bfefd1be43c6f9c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 11:42:40 +1100 Subject: Write settings json files out to unique files per test --- tests_gui_local/GuiBaseTest.py | 6 +++--- tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py | 2 +- tests_gui_local/onionshare_receive_mode_upload_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_public_mode_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_stay_open_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_test.py | 2 +- tests_gui_local/onionshare_share_mode_slug_persistent_test.py | 2 +- tests_gui_local/onionshare_share_mode_timer_test.py | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py index 8a8b127e..449a5b7e 100644 --- a/tests_gui_local/GuiBaseTest.py +++ b/tests_gui_local/GuiBaseTest.py @@ -19,7 +19,7 @@ from onionshare_gui.mode.receive_mode import ReceiveMode class GuiBaseTest(object): @staticmethod - def set_up(test_settings): + def set_up(test_settings, settings_filename): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -44,8 +44,7 @@ class GuiBaseTest(object): 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)) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], settings_filename, True) return gui @@ -54,6 +53,7 @@ class GuiBaseTest(object): def tear_down(): try: os.remove('/tmp/test.txt') + os.remove('/tmp/download.zip') shutil.rmtree('/tmp/OnionShare') except: pass diff --git a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py index f41a2ce2..32ea8656 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py @@ -11,7 +11,7 @@ class ReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings) + cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModePublicModeTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 8a7661dd..c59ac9c0 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -10,7 +10,7 @@ class ReceiveModeTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings) + cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py index 53d1fb8c..b6ce3a3c 100644 --- a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py +++ b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py @@ -10,7 +10,7 @@ class ShareModePublicModeTest(unittest.TestCase, GuiShareTest): test_settings = { "public_mode": True, } - cls.gui = GuiShareTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py index b398f654..7ff9aaf2 100644 --- a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py +++ b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py @@ -10,7 +10,7 @@ class ShareModeStayOpenTest(unittest.TestCase, GuiShareTest): test_settings = { "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 34532c59..9de0a767 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -9,7 +9,7 @@ class ShareModeTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py index b4c92f36..02cbdd27 100644 --- a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py +++ b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py @@ -13,7 +13,7 @@ class ShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): "save_private_key": True, "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_timer_test.py b/tests_gui_local/onionshare_share_mode_timer_test.py index 98902428..96c48443 100644 --- a/tests_gui_local/onionshare_share_mode_timer_test.py +++ b/tests_gui_local/onionshare_share_mode_timer_test.py @@ -11,7 +11,7 @@ class ShareModeTimerTest(unittest.TestCase, GuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiShareTest.set_up(test_settings) + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTimerTest') @classmethod def tearDownClass(cls): -- cgit v1.2.3-54-g00ecf From 297edbe6373ba1b1fcadc09bb5185b9c79a40df1 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 11:50:12 +1100 Subject: Revert "Write settings json files out to unique files per test" This reverts commit 6227c5879683420c185ffb114bfefd1be43c6f9c. --- tests_gui_local/GuiBaseTest.py | 6 +++--- tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py | 2 +- tests_gui_local/onionshare_receive_mode_upload_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_public_mode_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_stay_open_test.py | 2 +- tests_gui_local/onionshare_share_mode_download_test.py | 2 +- tests_gui_local/onionshare_share_mode_slug_persistent_test.py | 2 +- tests_gui_local/onionshare_share_mode_timer_test.py | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py index 449a5b7e..8a8b127e 100644 --- a/tests_gui_local/GuiBaseTest.py +++ b/tests_gui_local/GuiBaseTest.py @@ -19,7 +19,7 @@ from onionshare_gui.mode.receive_mode import ReceiveMode class GuiBaseTest(object): @staticmethod - def set_up(test_settings, settings_filename): + def set_up(test_settings): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -44,7 +44,8 @@ class GuiBaseTest(object): app = OnionShare(common, testonion, True, 0) web = Web(common, False, True) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + 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 @@ -53,7 +54,6 @@ class GuiBaseTest(object): def tear_down(): try: os.remove('/tmp/test.txt') - os.remove('/tmp/download.zip') shutil.rmtree('/tmp/OnionShare') except: pass diff --git a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py index 32ea8656..f41a2ce2 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py @@ -11,7 +11,7 @@ class ReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModePublicModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index c59ac9c0..8a7661dd 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -10,7 +10,7 @@ class ReceiveModeTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py index b6ce3a3c..53d1fb8c 100644 --- a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py +++ b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py @@ -10,7 +10,7 @@ class ShareModePublicModeTest(unittest.TestCase, GuiShareTest): test_settings = { "public_mode": True, } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py index 7ff9aaf2..b398f654 100644 --- a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py +++ b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py @@ -10,7 +10,7 @@ class ShareModeStayOpenTest(unittest.TestCase, GuiShareTest): test_settings = { "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 9de0a767..34532c59 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -9,7 +9,7 @@ class ShareModeTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py index 02cbdd27..b4c92f36 100644 --- a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py +++ b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py @@ -13,7 +13,7 @@ class ShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): "save_private_key": True, "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests_gui_local/onionshare_share_mode_timer_test.py b/tests_gui_local/onionshare_share_mode_timer_test.py index 96c48443..98902428 100644 --- a/tests_gui_local/onionshare_share_mode_timer_test.py +++ b/tests_gui_local/onionshare_share_mode_timer_test.py @@ -11,7 +11,7 @@ class ShareModeTimerTest(unittest.TestCase, GuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTimerTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): -- cgit v1.2.3-54-g00ecf From 7d8a47a53a46f6e96eaa5b49c0881c31e8157edf Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 12:18:34 +1100 Subject: Fix persistence tests, re-introduce separate settings json files, fix call to actual tests to use self. These can now be run with 'xvfb-run pytest tests_gui_local/' instead of via a shell script --- .travis.yml | 2 +- tests_gui_local/GuiBaseTest.py | 7 +++---- tests_gui_local/GuiShareTest.py | 14 ++++++++++++++ .../onionshare_receive_mode_upload_public_mode_test.py | 10 +++------- tests_gui_local/onionshare_receive_mode_upload_test.py | 10 +++------- .../onionshare_share_mode_download_public_mode_test.py | 10 +++------- .../onionshare_share_mode_download_stay_open_test.py | 10 +++------- tests_gui_local/onionshare_share_mode_download_test.py | 10 +++------- .../onionshare_share_mode_slug_persistent_test.py | 17 +++-------------- tests_gui_local/onionshare_share_mode_timer_test.py | 10 +++------- tests_gui_local/run_unit_tests.sh | 5 ----- 11 files changed, 39 insertions(+), 66 deletions(-) delete mode 100755 tests_gui_local/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index e0b5b822..5750536d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ before_script: # run CLI tests and local GUI tests script: - pytest --cov=onionshare tests/ - - cd tests_gui_local/ && xvfb-run ./run_unit_tests.sh + - xvfb-run pytest tests_gui_local/ diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py index 8a8b127e..138a279a 100644 --- a/tests_gui_local/GuiBaseTest.py +++ b/tests_gui_local/GuiBaseTest.py @@ -19,7 +19,7 @@ from onionshare_gui.mode.receive_mode import ReceiveMode class GuiBaseTest(object): @staticmethod - def set_up(test_settings): + def set_up(test_settings, settings_filename): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -44,10 +44,9 @@ class GuiBaseTest(object): 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)) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], settings_filename, True) + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) return gui @staticmethod diff --git a/tests_gui_local/GuiShareTest.py b/tests_gui_local/GuiShareTest.py index 8c3ea2b1..11df3167 100644 --- a/tests_gui_local/GuiShareTest.py +++ b/tests_gui_local/GuiShareTest.py @@ -24,6 +24,11 @@ class GuiShareTest(GuiBaseTest): # We should have timed out now self.assertEqual(mode.server_status.status, 0) + # Persistence tests + def have_same_slug(self, slug): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + # Share-specific tests def file_selection_widget_has_a_file(self): @@ -143,6 +148,15 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_download_tests(public_mode, stay_open) + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): + """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + slug = self.gui.share_mode.server_status.web.slug + self.run_all_share_mode_download_tests(public_mode, stay_open) + self.have_same_slug(slug) + + def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() diff --git a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py index f41a2ce2..5b685ef7 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py @@ -11,19 +11,15 @@ class ReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiReceiveTest.tear_down() + cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModePublicModeTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiReceiveTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_receive_mode_tests(self): - GuiReceiveTest.run_all_receive_mode_tests(self, True, True) + self.run_all_receive_mode_tests(True, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py index 8a7661dd..7171eaca 100644 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ b/tests_gui_local/onionshare_receive_mode_upload_test.py @@ -10,19 +10,15 @@ class ReceiveModeTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiReceiveTest.tear_down() + cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiReceiveTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_receive_mode_tests(self): - GuiReceiveTest.run_all_receive_mode_tests(self, False, True) + self.run_all_receive_mode_tests(False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py index 53d1fb8c..96cadb90 100644 --- a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py +++ b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py @@ -10,19 +10,15 @@ class ShareModePublicModeTest(unittest.TestCase, GuiShareTest): test_settings = { "public_mode": True, } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiShareTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_share_mode_tests(self): - GuiShareTest.run_all_share_mode_tests(self, True, False) + self.run_all_share_mode_tests(True, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py index b398f654..bb9ec2ea 100644 --- a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py +++ b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py @@ -10,19 +10,15 @@ class ShareModeStayOpenTest(unittest.TestCase, GuiShareTest): test_settings = { "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiShareTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_share_mode_tests(self): - GuiShareTest.run_all_share_mode_tests(self, False, True) + self.run_all_share_mode_tests(False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py index 34532c59..12b914b0 100644 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ b/tests_gui_local/onionshare_share_mode_download_test.py @@ -9,19 +9,15 @@ class ShareModeTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiShareTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_share_mode_tests(self): - GuiShareTest.run_all_share_mode_tests(self, False, False) + self.run_all_share_mode_tests(False, False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py index b4c92f36..25e4e093 100644 --- a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py +++ b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py @@ -13,26 +13,15 @@ class ShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): "save_private_key": True, "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiShareTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_share_mode_tests(self): - GuiShareTest.run_all_share_mode_tests(self, False, True) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=3) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + self.run_all_share_mode_persistent_tests(False, True) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_timer_test.py b/tests_gui_local/onionshare_share_mode_timer_test.py index 98902428..2e654c9d 100644 --- a/tests_gui_local/onionshare_share_mode_timer_test.py +++ b/tests_gui_local/onionshare_share_mode_timer_test.py @@ -11,19 +11,15 @@ class ShareModeTimerTest(unittest.TestCase, GuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() + cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTimerTest') @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): - GuiShareTest.run_all_common_setup_tests(self) + self.run_all_common_setup_tests() @pytest.mark.run(order=2) def test_run_all_share_mode_timer_tests(self): - GuiShareTest.run_all_share_mode_timer_tests(self, False) + self.run_all_share_mode_timer_tests(False) if __name__ == "__main__": unittest.main() diff --git a/tests_gui_local/run_unit_tests.sh b/tests_gui_local/run_unit_tests.sh deleted file mode 100755 index 7d207a57..00000000 --- a/tests_gui_local/run_unit_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^onionshare_`; do - pytest $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From 1be53c633a3bf65ad7adf2eb1bf869660e8bf91c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 11 Oct 2018 19:38:05 -0700 Subject: Remove the whole onionkey module, and generate onion keys using stem 1.7.0 --- install/requirements.txt | 2 +- onionshare/onion.py | 40 +++++++++++---- onionshare/onionkey.py | 129 ----------------------------------------------- 3 files changed, 30 insertions(+), 141 deletions(-) delete mode 100644 onionshare/onionkey.py diff --git a/install/requirements.txt b/install/requirements.txt index 32ec6887..9c8a4a5a 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -22,6 +22,6 @@ PyQt5-sip==4.19.12 PySocks==1.6.8 requests==2.19.1 six==1.11.0 -stem==1.6.0 +stem==1.7.0 urllib3==1.23 Werkzeug==0.14.1 diff --git a/onionshare/onion.py b/onionshare/onion.py index cb73e976..7122c208 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -21,10 +21,10 @@ along with this program. If not, see . from stem.control import Controller from stem import ProtocolError, SocketClosed from stem.connection import MissingPassword, UnreadableCookieFile, AuthenticationFailure +from Crypto.PublicKey import RSA import base64, os, sys, tempfile, shutil, urllib, platform, subprocess, time, shlex from distutils.version import LooseVersion as Version -from . import onionkey from . import common, strings from .settings import Settings @@ -441,8 +441,7 @@ class Onion(object): if self.settings.get('private_key'): key_content = self.settings.get('private_key') - # is the key a v2 key? - if onionkey.is_v2_key(key_content): + if self.is_v2_key(key_content): key_type = "RSA1024" # The below section is commented out because re-publishing # a pre-prepared v3 private key is currently unstable in Tor. @@ -458,29 +457,33 @@ class Onion(object): # key_type = "ED25519-V3" else: raise TorErrorProtocolError(strings._('error_invalid_private_key')) + else: + key_type = "NEW" # Work out if we can support v3 onion services, which are preferred if Version(self.tor_version) >= Version('0.3.3.1') and not self.settings.get('use_legacy_v2_onions'): - key_type = "ED25519-V3" - key_content = onionkey.generate_v3_private_key()[0] + key_content = "ED25519-V3" else: # fall back to v2 onion services - key_type = "RSA1024" - key_content = onionkey.generate_v2_private_key()[0] + key_content = "RSA1024" # v3 onions don't yet support basic auth. Our ticket: # https://github.com/micahflee/onionshare/issues/697 - if key_type == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): + if key_type == "NEW" and key_content == "ED25519-V3" and not self.settings.get('use_legacy_v2_onions'): basic_auth = None self.stealth = False - self.common.log('Onion', 'start_onion_service', 'key_type={}'.format(key_type)) + debug_message = 'key_type={}'.format(key_type) + if key_type == "NEW": + debug_message += ', key_content={}'.format(key_content) + self.common.log('Onion', 'start_onion_service', '{}'.format(debug_message)) + await_publication = True try: if basic_auth != None: - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, basic_auth=basic_auth, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, basic_auth=basic_auth, key_type=key_type, key_content=key_content) else: # if the stem interface is older than 1.5.0, basic_auth isn't a valid keyword arg - res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=True, key_type=key_type, key_content=key_content) + res = self.c.create_ephemeral_hidden_service({ 80: port }, await_publication=await_publication, key_type=key_type, key_content=key_content) except ProtocolError as e: raise TorErrorProtocolError(strings._('error_tor_protocol_error').format(e.args[0])) @@ -575,3 +578,18 @@ class Onion(object): return ('127.0.0.1', 9150) else: return (self.settings.get('socks_address'), self.settings.get('socks_port')) + + def is_v2_key(key): + """ + Helper function for determining if a key is RSA1024 (v2) or not. + """ + try: + # Import the key + key = RSA.importKey(base64.b64decode(key)) + # Is this a v2 Onion key? (1024 bits) If so, we should keep using it. + if key.n.bit_length() == 1024: + return True + else: + return False + except: + return False diff --git a/onionshare/onionkey.py b/onionshare/onionkey.py deleted file mode 100644 index d2c6ad17..00000000 --- a/onionshare/onionkey.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2017 Micah Lee - -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 . -""" - -import os -import sys - -import base64 -import hashlib -# Need sha3 if python version is older than 3.6, otherwise -# we can't use hashlib.sha3_256 -if sys.version_info < (3, 6): - import sha3 - -import nacl.signing - -from Crypto.PublicKey import RSA - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa - - -def stem_compatible_base64_blob_from_private_key(private_key_seed: bytes) -> str: - """ - Provides a base64-encoded private key for v3-style Onions. - """ - b = 256 - - def bit(h: bytes, i: int) -> int: - return (h[i // 8] >> (i % 8)) & 1 - - def encode_int(y: int) -> bytes: - bits = [(y >> i) & 1 for i in range(b)] - return b''.join([bytes([(sum([bits[i * 8 + j] << j for j in range(8)]))]) for i in range(b // 8)]) - - def expand_private_key(sk: bytes) -> bytes: - h = hashlib.sha512(sk).digest() - a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) - k = b''.join([bytes([h[i]]) for i in range(b // 8, b // 4)]) - assert len(k) == 32 - return encode_int(a) + k - - expanded_private_key = expand_private_key(private_key_seed) - return base64.b64encode(expanded_private_key).decode() - - -def onion_url_from_private_key(private_key_seed: bytes) -> str: - """ - Derives the public key (.onion hostname) from a v3-style - Onion private key. - """ - signing_key = nacl.signing.SigningKey(seed=private_key_seed) - public_key = bytes(signing_key.verify_key) - version = b'\x03' - checksum = hashlib.sha3_256(b".onion checksum" + public_key + version).digest()[:2] - onion_address = "http://{}.onion".format(base64.b32encode(public_key + checksum + version).decode().lower()) - return onion_address - - -def generate_v3_private_key(): - """ - Generates a private and public key for use with v3 style Onions. - Returns both the private key as well as the public key (.onion hostname) - """ - private_key_seed = os.urandom(32) - private_key = stem_compatible_base64_blob_from_private_key(private_key_seed) - onion_url = onion_url_from_private_key(private_key_seed) - return (private_key, onion_url) - - -def generate_v2_private_key(): - """ - Generates a private and public key for use with v2 style Onions. - Returns both the serialized private key (compatible with Stem) - as well as the public key (.onion hostname) - """ - # Generate v2 Onion Service private key - private_key = rsa.generate_private_key(public_exponent=65537, - key_size=1024, - backend=default_backend()) - hs_public_key = private_key.public_key() - - # Pre-generate the public key (.onion hostname) - der_format = hs_public_key.public_bytes(encoding=serialization.Encoding.DER, - format=serialization.PublicFormat.PKCS1) - - onion_url = base64.b32encode(hashlib.sha1(der_format).digest()[:-10]).lower().decode() - - # Generate Stem-compatible key content - pem_format = private_key.private_bytes(encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption()) - serialized_key = ''.join(pem_format.decode().split('\n')[1:-2]) - - return (serialized_key, onion_url) - - -def is_v2_key(key): - """ - Helper function for determining if a key is RSA1024 (v2) or not. - """ - try: - # Import the key - key = RSA.importKey(base64.b64decode(key)) - # Is this a v2 Onion key? (1024 bits) If so, we should keep using it. - if key.n.bit_length() == 1024: - return True - else: - return False - except: - return False - -- cgit v1.2.3-54-g00ecf From f8f705313b01386c5d8ab708d8d4034bf19f5d8c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 11 Oct 2018 19:55:53 -0700 Subject: Drop the PyNaCl dependency, because we are no longer generating v3 onion keys --- install/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/install/requirements.txt b/install/requirements.txt index 9c8a4a5a..20811f7b 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -16,7 +16,6 @@ pefile==2018.8.8 pycparser==2.18 pycryptodome==3.6.6 PyInstaller==3.4 -PyNaCl==1.2.1 PyQt5==5.11.2 PyQt5-sip==4.19.12 PySocks==1.6.8 -- cgit v1.2.3-54-g00ecf From b91f1d71bb9f3c5376b43011d948112e86eed50b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 11 Oct 2018 20:08:23 -0700 Subject: Bump python required version down to 3.5.3, and finish removing python3-nacl as a dependency --- .travis.yml | 1 + BUILD.md | 12 +++++++++--- install/build_rpm.sh | 2 +- stdeb.cfg | 6 +++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index e0b5b822..53fd661c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: python dist: trusty sudo: required python: + - "3.5.3" - "3.6" - "3.6-dev" - "3.7-dev" diff --git a/BUILD.md b/BUILD.md index 00d24cd2..c47dc427 100644 --- a/BUILD.md +++ b/BUILD.md @@ -11,11 +11,17 @@ cd onionshare Install the needed dependencies: -For Debian-like distros: `apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python` +For Debian-like distros: -On some older versions of Debian you may need to install pysha3 with `pip3 install pysha3` if python3-sha3 is not available. +``` +apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python +``` + +For Fedora-like distros: -For Fedora-like distros: `dnf install -y python3-flask python3-stem python3-qt5 python3-pynacl python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build` +``` +dnf install -y python3-flask python3-stem python3-qt5 python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build +``` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/install/build_rpm.sh b/install/build_rpm.sh index 3f7a68ac..7a34b271 100755 --- a/install/build_rpm.sh +++ b/install/build_rpm.sh @@ -9,7 +9,7 @@ VERSION=`cat share/version.txt` rm -r build dist >/dev/null 2>&1 # build binary package -python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-pynacl, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4" +python3 setup.py bdist_rpm --requires="python3-flask, python3-stem, python3-qt5, python3-cryptography, python3-crypto, python3-pysocks, nautilus-python, tor, obfs4" # install it echo "" diff --git a/stdeb.cfg b/stdeb.cfg index 2fc3d3bf..0980ef54 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-nacl, python3-socks, python-nautilus, tor, obfs4proxy +Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy +Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy Suite: bionic -X-Python3-Version: >= 3.6 +X-Python3-Version: >= 3.5.3 -- cgit v1.2.3-54-g00ecf From 61a98fbbc1a0b17bfe81c8fe74517ce7285c7b44 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 11 Oct 2018 20:18:23 -0700 Subject: Add instructions for making sure you have pytest in your path (which you don't by default in debian stretch) --- BUILD.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BUILD.md b/BUILD.md index c47dc427..3061c4a7 100644 --- a/BUILD.md +++ b/BUILD.md @@ -149,6 +149,12 @@ OnionShare includes PyTest unit tests. To run the tests, first install some depe pip3 install -r install/requirements-tests.txt ``` +You must have `pytest` in your path point to python 3's pytest. If necessary, you might need to make a symlink, like: + +```sh +ln -s /usr/bin/pytest-3 /usr/local/bin/pytest +``` + If you'd like to run the CLI-based tests that Travis runs: ```sh -- cgit v1.2.3-54-g00ecf From 12180f08dc117ce1dbd72017b8af97cc77891c9a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 11 Oct 2018 20:48:46 -0700 Subject: Remove python3-cryptography dependency --- BUILD.md | 4 ++-- stdeb.cfg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD.md b/BUILD.md index 3061c4a7..d8150799 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,13 +14,13 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python +apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python ``` For Fedora-like distros: ``` -dnf install -y python3-flask python3-stem python3-qt5 python3-cryptography python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build +dnf install -y python3-flask python3-stem python3-qt5 python3-crypto python3-pysocks nautilus-python tor obfs4 python3-pytest rpm-build ``` After that you can try both the CLI and the GUI version of OnionShare: diff --git a/stdeb.cfg b/stdeb.cfg index 0980ef54..6729d1b7 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-cryptography, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy +Depends3: python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy +Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy Suite: bionic X-Python3-Version: >= 3.5.3 -- cgit v1.2.3-54-g00ecf From b758ac4d0aeb3c7288125b6a597f62235ddc8042 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 18:53:03 +1100 Subject: Beginning to move the Tor tests into tests_gui and inheriting what we can from the local tests to avoid code reuse. Add --runtor flag in pytest to run these --- .travis.yml | 2 +- tests_gui/GuiBaseTest.py | 275 +++++++++++++++++++++ tests_gui/GuiReceiveTest.py | 45 ++++ tests_gui/GuiShareTest.py | 168 +++++++++++++ tests_gui/TorGuiBaseTest.py | 154 ++++++++++++ tests_gui/TorGuiReceiveTest.py | 51 ++++ tests_gui/TorGuiShareTest.py | 70 ++++++ tests_gui/__init__.py | 0 tests_gui/conftest.py | 176 +++++++++++++ ...onshare_receive_mode_upload_public_mode_test.py | 25 ++ .../local_onionshare_receive_mode_upload_test.py | 24 ++ ...onshare_share_mode_download_public_mode_test.py | 24 ++ ...nionshare_share_mode_download_stay_open_test.py | 24 ++ .../local_onionshare_share_mode_download_test.py | 23 ++ ...l_onionshare_share_mode_slug_persistent_test.py | 27 ++ .../local_onionshare_share_mode_timer_test.py | 25 ++ ...onshare_receive_mode_upload_public_mode_test.py | 27 ++ tests_gui/onionshare_receive_mode_upload_test.py | 26 ++ ...onshare_share_mode_download_public_mode_test.py | 26 ++ ...nionshare_share_mode_download_stay_open_test.py | 26 ++ tests_gui/onionshare_share_mode_download_test.py | 25 ++ tests_gui/onionshare_share_mode_persistent_test.py | 30 +++ tests_gui/onionshare_share_mode_stealth_test.py | 34 +++ ...nshare_share_mode_tor_connection_killed_test.py | 42 ++++ tests_gui_local/GuiBaseTest.py | 275 --------------------- tests_gui_local/GuiReceiveTest.py | 45 ---- tests_gui_local/GuiShareTest.py | 168 ------------- tests_gui_local/__init__.py | 0 tests_gui_local/conftest.py | 160 ------------ ...onshare_receive_mode_upload_public_mode_test.py | 25 -- .../onionshare_receive_mode_upload_test.py | 24 -- ...onshare_share_mode_download_public_mode_test.py | 24 -- ...nionshare_share_mode_download_stay_open_test.py | 24 -- .../onionshare_share_mode_download_test.py | 23 -- .../onionshare_share_mode_slug_persistent_test.py | 27 -- .../onionshare_share_mode_timer_test.py | 25 -- tests_gui_tor/__init__.py | 0 tests_gui_tor/commontests.py | 61 ----- tests_gui_tor/conftest.py | 163 ------------ .../onionshare_receive_mode_upload_test.py | 190 -------------- ...onshare_receive_mode_upload_test_public_mode.py | 190 -------------- .../onionshare_share_mode_download_test.py | 193 --------------- ...onshare_share_mode_download_test_public_mode.py | 201 --------------- ...nionshare_share_mode_download_test_stay_open.py | 213 ---------------- .../onionshare_share_mode_persistent_test.py | 185 -------------- .../onionshare_share_mode_stealth_test.py | 180 -------------- ...nshare_share_mode_tor_connection_killed_test.py | 185 -------------- .../onionshare_tor_connection_killed_test.py | 185 -------------- tests_gui_tor/run_unit_tests.sh | 5 - 49 files changed, 1348 insertions(+), 2772 deletions(-) create mode 100644 tests_gui/GuiBaseTest.py create mode 100644 tests_gui/GuiReceiveTest.py create mode 100644 tests_gui/GuiShareTest.py create mode 100644 tests_gui/TorGuiBaseTest.py create mode 100644 tests_gui/TorGuiReceiveTest.py create mode 100644 tests_gui/TorGuiShareTest.py create mode 100644 tests_gui/__init__.py create mode 100644 tests_gui/conftest.py create mode 100644 tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py create mode 100644 tests_gui/local_onionshare_receive_mode_upload_test.py create mode 100644 tests_gui/local_onionshare_share_mode_download_public_mode_test.py create mode 100644 tests_gui/local_onionshare_share_mode_download_stay_open_test.py create mode 100644 tests_gui/local_onionshare_share_mode_download_test.py create mode 100644 tests_gui/local_onionshare_share_mode_slug_persistent_test.py create mode 100644 tests_gui/local_onionshare_share_mode_timer_test.py create mode 100644 tests_gui/onionshare_receive_mode_upload_public_mode_test.py create mode 100644 tests_gui/onionshare_receive_mode_upload_test.py create mode 100644 tests_gui/onionshare_share_mode_download_public_mode_test.py create mode 100644 tests_gui/onionshare_share_mode_download_stay_open_test.py create mode 100644 tests_gui/onionshare_share_mode_download_test.py create mode 100644 tests_gui/onionshare_share_mode_persistent_test.py create mode 100644 tests_gui/onionshare_share_mode_stealth_test.py create mode 100644 tests_gui/onionshare_share_mode_tor_connection_killed_test.py delete mode 100644 tests_gui_local/GuiBaseTest.py delete mode 100644 tests_gui_local/GuiReceiveTest.py delete mode 100644 tests_gui_local/GuiShareTest.py delete mode 100644 tests_gui_local/__init__.py delete mode 100644 tests_gui_local/conftest.py delete mode 100644 tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py delete mode 100644 tests_gui_local/onionshare_receive_mode_upload_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_public_mode_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_stay_open_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_download_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_slug_persistent_test.py delete mode 100644 tests_gui_local/onionshare_share_mode_timer_test.py delete mode 100644 tests_gui_tor/__init__.py delete mode 100644 tests_gui_tor/commontests.py delete mode 100644 tests_gui_tor/conftest.py delete mode 100644 tests_gui_tor/onionshare_receive_mode_upload_test.py delete mode 100644 tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py delete mode 100644 tests_gui_tor/onionshare_share_mode_download_test.py delete mode 100644 tests_gui_tor/onionshare_share_mode_download_test_public_mode.py delete mode 100644 tests_gui_tor/onionshare_share_mode_download_test_stay_open.py delete mode 100644 tests_gui_tor/onionshare_share_mode_persistent_test.py delete mode 100644 tests_gui_tor/onionshare_share_mode_stealth_test.py delete mode 100644 tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py delete mode 100644 tests_gui_tor/onionshare_tor_connection_killed_test.py delete mode 100755 tests_gui_tor/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index 5750536d..c54c205d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ before_script: # run CLI tests and local GUI tests script: - pytest --cov=onionshare tests/ - - xvfb-run pytest tests_gui_local/ + - xvfb-run pytest tests_gui/ -vvv diff --git a/tests_gui/GuiBaseTest.py b/tests_gui/GuiBaseTest.py new file mode 100644 index 00000000..79543468 --- /dev/null +++ b/tests_gui/GuiBaseTest.py @@ -0,0 +1,275 @@ +import json +import os +import requests +import shutil +import socket +import socks + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + + +class GuiBaseTest(object): + @staticmethod + def set_up(test_settings, settings_filename): + '''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) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) + return gui + + @staticmethod + def tear_down(): + try: + os.remove('/tmp/test.txt') + shutil.rmtree('/tmp/OnionShare') + except: + pass + + + def gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + + def windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + + def settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + + def server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + + def click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if type(mode) == ReceiveMode: + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if type(mode) == ShareMode: + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + + def click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) + + + def history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + + def history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + self.assertFalse(mode.history.isVisible()) + + + def history_is_visible(self, mode): + '''Test that the History section is visible''' + self.assertTrue(mode.history.isVisible()) + + + def server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) + + + def server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + + + def settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + + def a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + + def a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + + def have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + + + def url_description_shown(self, mode): + '''Test that the URL label is showing''' + self.assertTrue(mode.server_status.url_description.isVisible()) + + + def have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + + def server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + + + def web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + + def history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) + + + def counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + self.assertEquals(mode.history.completed_count, count) + + + def server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) + + + def web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + + def server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if type(mode) == ReceiveMode: + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + if type(mode) == ShareMode: + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + def run_all_common_setup_tests(self): + self.gui_loaded() + self.windowTitle_seen() + self.settings_button_is_visible() + self.server_status_bar_is_visible() + + diff --git a/tests_gui/GuiReceiveTest.py b/tests_gui/GuiReceiveTest.py new file mode 100644 index 00000000..1fa5c4dc --- /dev/null +++ b/tests_gui/GuiReceiveTest.py @@ -0,0 +1,45 @@ +import os +import requests +from PyQt5 import QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiReceiveTest(GuiBaseTest): + def upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + self.click_mode(self.gui.receive_mode) + self.history_is_not_visible(self.gui.receive_mode) + self.click_toggle_history(self.gui.receive_mode) + self.history_is_visible(self.gui.receive_mode) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.server_status_indicator_says_starting(self.gui.receive_mode) + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.receive_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.receive_mode, public_mode) + self.url_description_shown(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode) + self.server_status_indicator_says_started(self.gui.receive_mode) + self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.counter_incremented(self.gui.receive_mode, 1) + self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.counter_incremented(self.gui.receive_mode, 2) + self.history_indicator(self.gui.receive_mode, public_mode) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.a_server_is_started(self.gui.receive_mode) + self.history_indicator(self.gui.receive_mode, public_mode) + diff --git a/tests_gui/GuiShareTest.py b/tests_gui/GuiShareTest.py new file mode 100644 index 00000000..34149dec --- /dev/null +++ b/tests_gui/GuiShareTest.py @@ -0,0 +1,168 @@ +import socks +import zipfile +from PyQt5 import QtCore, QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiShareTest(GuiBaseTest): + # Auto-stop timer tests + + def set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + + def timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + + def server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Persistence tests + def have_same_slug(self, slug): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + + # Share-specific tests + + def file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + + def deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + + def file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + + def add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + + def add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + self.click_mode(self.gui.share_mode) + self.file_selection_widget_has_a_file() + self.history_is_not_visible(self.gui.share_mode) + self.click_toggle_history(self.gui.share_mode) + self.history_is_visible(self.gui.share_mode) + self.deleting_only_file_hides_delete_button() + self.add_a_file_and_delete_using_its_delete_widget() + self.file_selection_widget_readd_files() + + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.share_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.share_mode, public_mode) + self.url_description_shown(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode) + self.server_status_indicator_says_started(self.gui.share_mode) + + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.download_share(public_mode) + self.history_widgets_present(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + self.add_button_visible() + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.a_server_is_started(self.gui.share_mode) + self.history_indicator(self.gui.share_mode, public_mode) + + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + self.run_all_share_mode_download_tests(public_mode, stay_open) + + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): + """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + slug = self.gui.share_mode.server_status.web.slug + self.run_all_share_mode_download_tests(public_mode, stay_open) + self.have_same_slug(slug) + + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_timeout(self.gui.share_mode, 5) + self.run_all_share_mode_started_tests(public_mode) + self.timeout_widget_hidden(self.gui.share_mode) + self.server_timed_out(self.gui.share_mode, 10000) + self.web_service_is_stopped() + diff --git a/tests_gui/TorGuiBaseTest.py b/tests_gui/TorGuiBaseTest.py new file mode 100644 index 00000000..1c313726 --- /dev/null +++ b/tests_gui/TorGuiBaseTest.py @@ -0,0 +1,154 @@ +import json +import os +import requests +import shutil +import socket +import socks + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + +from .GuiBaseTest import GuiBaseTest + +class TorGuiBaseTest(GuiBaseTest): + @staticmethod + def set_up(test_settings, settings_filename): + '''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['connection_type'] = 'automatic' + 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, False, 0) + + web = Web(common, False, False) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), False) + return gui + + def history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + # Set up connecting to the onion + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug) + else: + path = 'http://{}/upload'.format(self.gui.app.onion_host) + response = session.post(path, files=files) + QtTest.QTest.qWait(4000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + path = "http://{}/download".format(self.gui.app.onion_host) + else: + path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug) + response = session.get(path) + QtTest.QTest.qWait(4000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + def a_server_is_started(self, mode): + '''Test that the server has started (overriding from local tests to wait for longer)''' + QtTest.QTest.qWait(45000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + def have_an_onion_service(self): + '''Test that we have a valid Onion URL''' + self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + + def web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) + s = socks.socksocket() + s.settimeout(60) + s.connect((self.gui.app.onion_host, 80)) + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def cancel_the_share(self, mode): + '''Test that we can cancel this share before it's started up ''' + QtTest.QTest.mousePress(self.gui.mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(1000) + QtTest.QTest.mouseRelease(self.gui.mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.mode.server_status.status, 0) + + # Stealth tests + def copy_have_hidserv_auth_button(self, mode): + '''Test that the Copy HidservAuth button is shown''' + self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) + + def hidserv_auth_string(self): + '''Test the validity of the HidservAuth string''' + self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) + + # Miscellaneous tests + def tor_killed_statusbar_message_shown(self, mode): + '''Test that the status bar message shows Tor was disconnected''' + self.gui.app.onion.cleanup(stop_tor=True) + QtTest.QTest.qWait(2500) + self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests_gui/TorGuiReceiveTest.py b/tests_gui/TorGuiReceiveTest.py new file mode 100644 index 00000000..67b6a811 --- /dev/null +++ b/tests_gui/TorGuiReceiveTest.py @@ -0,0 +1,51 @@ +import os +import requests +from PyQt5 import QtTest +from .TorGuiBaseTest import TorGuiBaseTest + +class TorGuiReceiveTest(TorGuiBaseTest): + + def upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) + else: + path = 'http://{}/upload'.format(self.gui.app.onion_host) + response = session.post(path, files=files) + QtTest.QTest.qWait(4000) + self.assertTrue(os.path.isfile(expected_file)) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + self.click_mode(self.gui.receive_mode) + self.history_is_not_visible(self.gui.receive_mode) + self.click_toggle_history(self.gui.receive_mode) + self.history_is_visible(self.gui.receive_mode) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.server_status_indicator_says_starting(self.gui.receive_mode) + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.receive_mode) + self.a_web_server_is_running() + self.have_an_onion_service() + self.have_a_slug(self.gui.receive_mode, public_mode) + self.url_description_shown(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode) + self.server_status_indicator_says_started(self.gui.receive_mode) + self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.counter_incremented(self.gui.receive_mode, 1) + self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.counter_incremented(self.gui.receive_mode, 2) + self.history_indicator(self.gui.receive_mode, public_mode) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.a_server_is_started(self.gui.receive_mode) + self.history_indicator(self.gui.receive_mode, public_mode) + diff --git a/tests_gui/TorGuiShareTest.py b/tests_gui/TorGuiShareTest.py new file mode 100644 index 00000000..8d6358c3 --- /dev/null +++ b/tests_gui/TorGuiShareTest.py @@ -0,0 +1,70 @@ +import requests +import socks +import zipfile +from PyQt5 import QtCore, QtTest +from .TorGuiBaseTest import TorGuiBaseTest +from .GuiShareTest import GuiShareTest + +class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): + def download_share(self, public_mode): + # Set up connecting to the onion + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + + # Download files + if public_mode: + path = "http://{}/download".format(self.gui.app.onion_host) + else: + path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug) + response = session.get(path, stream=True) + QtTest.QTest.qWait(4000) + + if response.status_code == 200: + with open('/tmp/download.zip', 'wb') as file_to_write: + for chunk in response.iter_content(chunk_size=128): + file_to_write.write(chunk) + file_to_write.close() + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(4000) + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + # Persistence tests + def have_same_onion(self, onion): + '''Test that we have the same onion''' + self.assertEqual(self.gui.app.onion_host, onion) + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.share_mode) + self.a_web_server_is_running() + self.have_an_onion_service() + self.have_a_slug(self.gui.share_mode, public_mode) + self.url_description_shown(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode) + self.server_status_indicator_says_started(self.gui.share_mode) + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): + """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + slug = self.gui.share_mode.server_status.web.slug + onion = self.gui.app.onion_host + self.run_all_share_mode_download_tests(public_mode, stay_open) + self.have_same_onion(onion) + self.have_same_slug(slug) + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_timeout(self.gui.share_mode, 5) + self.run_all_share_mode_started_tests(public_mode) + self.timeout_widget_hidden(self.gui.share_mode) + self.server_timed_out(self.gui.share_mode, 10000) + self.web_service_is_stopped() + diff --git a/tests_gui/__init__.py b/tests_gui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests_gui/conftest.py b/tests_gui/conftest.py new file mode 100644 index 00000000..62108e05 --- /dev/null +++ b/tests_gui/conftest.py @@ -0,0 +1,176 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +def pytest_addoption(parser): + parser.addoption( + "--runtor", action="store_true", default=False, help="run tor tests" + ) + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--runtor"): + # --runtor given in cli: do not skip tor tests + return + skip_tor = pytest.mark.skip(reason="need --runtor option to run") + for item in items: + if "tor" in item.keywords: + item.add_marker(skip_tor) + + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py new file mode 100644 index 00000000..1b5722c7 --- /dev/null +++ b/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_receive_mode_upload_test.py b/tests_gui/local_onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..14a0377d --- /dev/null +++ b/tests_gui/local_onionshare_receive_mode_upload_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_public_mode_test.py b/tests_gui/local_onionshare_share_mode_download_public_mode_test.py new file mode 100644 index 00000000..6dcb94df --- /dev/null +++ b/tests_gui/local_onionshare_share_mode_download_public_mode_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_stay_open_test.py b/tests_gui/local_onionshare_share_mode_download_stay_open_test.py new file mode 100644 index 00000000..62eafff7 --- /dev/null +++ b/tests_gui/local_onionshare_share_mode_download_stay_open_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_test.py b/tests_gui/local_onionshare_share_mode_download_test.py new file mode 100644 index 00000000..98270523 --- /dev/null +++ b/tests_gui/local_onionshare_share_mode_download_test.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_slug_persistent_test.py b/tests_gui/local_onionshare_share_mode_slug_persistent_test.py new file mode 100644 index 00000000..9e171ba4 --- /dev/null +++ b/tests_gui/local_onionshare_share_mode_slug_persistent_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_persistent_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_timer_test.py b/tests_gui/local_onionshare_share_mode_timer_test.py new file mode 100644 index 00000000..b13971b0 --- /dev/null +++ b/tests_gui/local_onionshare_share_mode_timer_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_all_share_mode_timer_tests(self): + self.run_all_share_mode_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/onionshare_receive_mode_upload_public_mode_test.py new file mode 100644 index 00000000..8561cbce --- /dev/null +++ b/tests_gui/onionshare_receive_mode_upload_public_mode_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiReceiveTest import TorGuiReceiveTest + +class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_receive_mode_upload_test.py b/tests_gui/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..6dac4bc8 --- /dev/null +++ b/tests_gui/onionshare_receive_mode_upload_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiReceiveTest import TorGuiReceiveTest + +class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_public_mode_test.py b/tests_gui/onionshare_share_mode_download_public_mode_test.py new file mode 100644 index 00000000..3a17b47e --- /dev/null +++ b/tests_gui/onionshare_share_mode_download_public_mode_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_stay_open_test.py b/tests_gui/onionshare_share_mode_download_stay_open_test.py new file mode 100644 index 00000000..ddb513dd --- /dev/null +++ b/tests_gui/onionshare_share_mode_download_stay_open_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_test.py b/tests_gui/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..17c94215 --- /dev/null +++ b/tests_gui/onionshare_share_mode_download_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_persistent_test.py b/tests_gui/onionshare_share_mode_persistent_test.py new file mode 100644 index 00000000..415ce42e --- /dev/null +++ b/tests_gui/onionshare_share_mode_persistent_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class LocalShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "use_legacy_v2_onions": True, + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_persistent_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_stealth_test.py b/tests_gui/onionshare_share_mode_stealth_test.py new file mode 100644 index 00000000..56303226 --- /dev/null +++ b/tests_gui/onionshare_share_mode_stealth_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "use_legacy_v2_onions": True, + "use_stealth": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') + + @pytest.mark.run(order=1) + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(False) + + @pytest.mark.run(order=3) + def test_copy_have_hidserv_auth_button(self): + self.copy_have_hidserv_auth_button(self.gui.share_mode) + + @pytest.mark.run(order=4) + def test_hidserv_auth_string(self): + self.hidserv_auth_string() + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui/onionshare_share_mode_tor_connection_killed_test.py new file mode 100644 index 00000000..6ebdec97 --- /dev/null +++ b/tests_gui/onionshare_share_mode_tor_connection_killed_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(False) + + @pytest.mark.run(order=3) + @pytest.mark.tor + def test_tor_killed_statusbar_message_shown(self): + self.tor_killed_statusbar_message_shown(self.gui.share_mode) + + @pytest.mark.run(order=4) + @pytest.mark.tor + def test_server_is_stopped(self): + self.server_is_stopped(self.gui.share_mode, False) + + @pytest.mark.run(order=5) + @pytest.mark.tor + def test_web_service_is_stopped(self): + self.web_service_is_stopped() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui_local/GuiBaseTest.py b/tests_gui_local/GuiBaseTest.py deleted file mode 100644 index 138a279a..00000000 --- a/tests_gui_local/GuiBaseTest.py +++ /dev/null @@ -1,275 +0,0 @@ -import json -import os -import requests -import shutil -import socket -import socks - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - - -class GuiBaseTest(object): - @staticmethod - def set_up(test_settings, settings_filename): - '''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) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) - return gui - - @staticmethod - def tear_down(): - try: - os.remove('/tmp/test.txt') - shutil.rmtree('/tmp/OnionShare') - except: - pass - - - def gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - - def windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - - def settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - - def server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - - def click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if type(mode) == ReceiveMode: - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if type(mode) == ShareMode: - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - - def click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' - currently_visible = mode.history.isVisible() - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertEqual(mode.history.isVisible(), not currently_visible) - - - def history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) - else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, self.gui.share_mode.web.slug) - r = requests.get(url) - QtTest.QTest.qWait(2000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - - def history_is_not_visible(self, mode): - '''Test that the History section is not visible''' - self.assertFalse(mode.history.isVisible()) - - - def history_is_visible(self, mode): - '''Test that the History section is visible''' - self.assertTrue(mode.history.isVisible()) - - - def server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 1) - - - def server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - - def settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - - def a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - - def a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - - def have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - - - def url_description_shown(self, mode): - '''Test that the URL label is showing''' - self.assertTrue(mode.server_status.url_description.isVisible()) - - - def have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - self.assertTrue(mode.server_status.copy_url_button.isVisible()) - - - def server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) - if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - - - def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - - def history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - self.assertFalse(mode.history.empty.isVisible()) - self.assertTrue(mode.history.not_empty.isVisible()) - - - def counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) - - - def server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) - - - def web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - - def server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) - if type(mode) == ShareMode: - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - - def run_all_common_setup_tests(self): - self.gui_loaded() - self.windowTitle_seen() - self.settings_button_is_visible() - self.server_status_bar_is_visible() - - diff --git a/tests_gui_local/GuiReceiveTest.py b/tests_gui_local/GuiReceiveTest.py deleted file mode 100644 index 1fa5c4dc..00000000 --- a/tests_gui_local/GuiReceiveTest.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import requests -from PyQt5 import QtTest -from .GuiBaseTest import GuiBaseTest - -class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - self.click_mode(self.gui.receive_mode) - self.history_is_not_visible(self.gui.receive_mode) - self.click_toggle_history(self.gui.receive_mode) - self.history_is_visible(self.gui.receive_mode) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.server_status_indicator_says_starting(self.gui.receive_mode) - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.receive_mode) - self.a_web_server_is_running() - self.have_a_slug(self.gui.receive_mode, public_mode) - self.url_description_shown(self.gui.receive_mode) - self.have_copy_url_button(self.gui.receive_mode) - self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - self.upload_file(public_mode, '/tmp/OnionShare/test.txt') - self.history_widgets_present(self.gui.receive_mode) - self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') - self.counter_incremented(self.gui.receive_mode, 2) - self.history_indicator(self.gui.receive_mode, public_mode) - self.server_is_stopped(self.gui.receive_mode, False) - self.web_service_is_stopped() - self.server_status_indicator_says_closed(self.gui.receive_mode, False) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.a_server_is_started(self.gui.receive_mode) - self.history_indicator(self.gui.receive_mode, public_mode) - diff --git a/tests_gui_local/GuiShareTest.py b/tests_gui_local/GuiShareTest.py deleted file mode 100644 index 11df3167..00000000 --- a/tests_gui_local/GuiShareTest.py +++ /dev/null @@ -1,168 +0,0 @@ -import socks -import zipfile -from PyQt5 import QtCore, QtTest -from .GuiBaseTest import GuiBaseTest - -class GuiShareTest(GuiBaseTest): - # Auto-stop timer tests - - def set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - - def timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - - def server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Persistence tests - def have_same_slug(self, slug): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - - # Share-specific tests - - def file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - - def deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - - def add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - - def file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - - def add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - - def download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - - def add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - - def run_all_share_mode_setup_tests(self): - """Tests in share mode prior to starting a share""" - self.click_mode(self.gui.share_mode) - self.file_selection_widget_has_a_file() - self.history_is_not_visible(self.gui.share_mode) - self.click_toggle_history(self.gui.share_mode) - self.history_is_visible(self.gui.share_mode) - self.deleting_only_file_hides_delete_button() - self.add_a_file_and_delete_using_its_delete_widget() - self.file_selection_widget_readd_files() - - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.server_status_indicator_says_starting(self.gui.share_mode) - self.add_delete_buttons_hidden() - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.share_mode) - self.a_web_server_is_running() - self.have_a_slug(self.gui.share_mode, public_mode) - self.url_description_shown(self.gui.share_mode) - self.have_copy_url_button(self.gui.share_mode) - self.server_status_indicator_says_started(self.gui.share_mode) - self.web_page(self.gui.share_mode, 'Total size', public_mode) - - - def run_all_share_mode_download_tests(self, public_mode, stay_open): - """Tests in share mode after downloading a share""" - self.download_share(public_mode) - self.history_widgets_present(self.gui.share_mode) - self.server_is_stopped(self.gui.share_mode, stay_open) - self.web_service_is_stopped() - self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - self.add_button_visible() - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.a_server_is_started(self.gui.share_mode) - self.history_indicator(self.gui.share_mode, public_mode) - - - def run_all_share_mode_tests(self, public_mode, stay_open): - """End-to-end share tests""" - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(public_mode) - self.run_all_share_mode_download_tests(public_mode, stay_open) - - - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): - """Same as end-to-end share tests but also test the slug is the same on multiple shared""" - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(public_mode) - slug = self.gui.share_mode.server_status.web.slug - self.run_all_share_mode_download_tests(public_mode, stay_open) - self.have_same_slug(slug) - - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - self.run_all_share_mode_setup_tests() - self.set_timeout(self.gui.share_mode, 5) - self.run_all_share_mode_started_tests(public_mode) - self.timeout_widget_hidden(self.gui.share_mode) - self.server_timed_out(self.gui.share_mode, 10000) - self.web_service_is_stopped() - diff --git a/tests_gui_local/__init__.py b/tests_gui_local/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests_gui_local/conftest.py b/tests_gui_local/conftest.py deleted file mode 100644 index 8ac7efb8..00000000 --- a/tests_gui_local/conftest.py +++ /dev/null @@ -1,160 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - return common.Common() - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py deleted file mode 100644 index 5b685ef7..00000000 --- a/tests_gui_local/onionshare_receive_mode_upload_public_mode_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiReceiveTest import GuiReceiveTest - -class ReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModePublicModeTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_receive_mode_upload_test.py b/tests_gui_local/onionshare_receive_mode_upload_test.py deleted file mode 100644 index 7171eaca..00000000 --- a/tests_gui_local/onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiReceiveTest import GuiReceiveTest - -class ReceiveModeTest(unittest.TestCase, GuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py b/tests_gui_local/onionshare_share_mode_download_public_mode_test.py deleted file mode 100644 index 96cadb90..00000000 --- a/tests_gui_local/onionshare_share_mode_download_public_mode_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class ShareModePublicModeTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py b/tests_gui_local/onionshare_share_mode_download_stay_open_test.py deleted file mode 100644 index bb9ec2ea..00000000 --- a/tests_gui_local/onionshare_share_mode_download_stay_open_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class ShareModeStayOpenTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_download_test.py b/tests_gui_local/onionshare_share_mode_download_test.py deleted file mode 100644 index 12b914b0..00000000 --- a/tests_gui_local/onionshare_share_mode_download_test.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class ShareModeTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py b/tests_gui_local/onionshare_share_mode_slug_persistent_test.py deleted file mode 100644 index 25e4e093..00000000 --- a/tests_gui_local/onionshare_share_mode_slug_persistent_test.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class ShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_persistent_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_local/onionshare_share_mode_timer_test.py b/tests_gui_local/onionshare_share_mode_timer_test.py deleted file mode 100644 index 2e654c9d..00000000 --- a/tests_gui_local/onionshare_share_mode_timer_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class ShareModeTimerTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = GuiShareTest.set_up(test_settings, 'ShareModeTimerTest') - - @pytest.mark.run(order=1) - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(order=2) - def test_run_all_share_mode_timer_tests(self): - self.run_all_share_mode_timer_tests(False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/__init__.py b/tests_gui_tor/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests_gui_tor/commontests.py b/tests_gui_tor/commontests.py deleted file mode 100644 index 89ebf669..00000000 --- a/tests_gui_tor/commontests.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -import requests -import socket -import socks -import zipfile - -from PyQt5 import QtCore, QtTest -from onionshare import strings - -from tests_gui_local import CommonTests as LocalCommonTests - -class CommonTests(LocalCommonTests): - def test_a_server_is_started(self, mode): - '''Test that the server has started (overriding from local tests to wait for longer)''' - QtTest.QTest.qWait(45000) - # Should now be in SERVER_STARTED state - if mode == 'receive': - self.assertEqual(self.gui.receive_mode.server_status.status, 2) - if mode == 'share': - self.assertEqual(self.gui.share_mode.server_status.status, 2) - - def test_have_an_onion_service(self): - '''Test that we have a valid Onion URL''' - self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - - def test_cancel_the_share(self, mode): - '''Test that we can cancel this share before it's started up ''' - if mode == 'share': - QtTest.QTest.mousePress(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(1000) - QtTest.QTest.mouseRelease(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.status, 0) - - if mode == 'receive': - QtTest.QTest.mousePress(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(1000) - QtTest.QTest.mouseRelease(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.receive_mode.server_status.status, 0) - - # Stealth tests - def test_copy_have_hidserv_auth_button(self, mode): - '''Test that the Copy HidservAuth button is shown''' - if mode == 'share': - self.assertTrue(self.gui.share_mode.server_status.copy_hidservauth_button.isVisible()) - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.server_status.copy_hidservauth_button.isVisible()) - - def test_hidserv_auth_string(self): - '''Test the validity of the HidservAuth string''' - self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) - - - # Miscellaneous tests - def test_tor_killed_statusbar_message_shown(self, mode): - '''Test that the status bar message shows Tor was disconnected''' - self.gui.app.onion.cleanup(stop_tor=True) - QtTest.QTest.qWait(2500) - if mode == 'share': - self.assertTrue(self.gui.share_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) - if mode == 'receive': - self.assertTrue(self.gui.receive_mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests_gui_tor/conftest.py b/tests_gui_tor/conftest.py deleted file mode 100644 index 3ae6fd52..00000000 --- a/tests_gui_tor/conftest.py +++ /dev/null @@ -1,163 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings, strings - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - _common = common.Common() - _common.settings = settings.Settings(_common) - strings.load_strings(_common) - return _common - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test.py b/tests_gui_tor/onionshare_receive_mode_upload_test.py deleted file mode 100644 index 7c340037..00000000 --- a/tests_gui_tor/onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=6) - def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') - - @pytest.mark.run(order=6) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'receive') - - @pytest.mark.run(order=7) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'receive') - - @pytest.mark.run(order=8) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') - - @pytest.mark.run(order=8) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - - @pytest.mark.run(order=9) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') - - @pytest.mark.run(order=10) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=11) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') - - @pytest.mark.run(order=12) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', False) - - @pytest.mark.run(order=15) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=20) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') - - @pytest.mark.run(order=21) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') - - @pytest.mark.run(order=22) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') - - @pytest.mark.run(order=23) - def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', False) - - @pytest.mark.run(order=24) - def test_upload_file(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=25) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') - - @pytest.mark.run(order=26) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) - - @pytest.mark.run(order=27) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, False, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=28) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) - - @pytest.mark.run(order=29) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) - - @pytest.mark.run(order=30) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=31) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py b/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py deleted file mode 100644 index 65bf5c89..00000000 --- a/tests_gui_tor/onionshare_receive_mode_upload_test_public_mode.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - os.remove('/tmp/OnionShare/test.txt') - os.remove('/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_click_mode(self): - CommonTests.test_click_mode(self, 'receive') - - @pytest.mark.run(order=6) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'receive') - - @pytest.mark.run(order=7) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'receive') - - @pytest.mark.run(order=8) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'receive') - - @pytest.mark.run(order=9) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'receive') - - @pytest.mark.run(order=10) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'receive') - - @pytest.mark.run(order=11) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=12) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'receive') - - @pytest.mark.run(order=13) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=14) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'receive', True) - - @pytest.mark.run(order=15) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=20) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'receive') - - @pytest.mark.run(order=21) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'receive') - - @pytest.mark.run(order=22) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'receive') - - @pytest.mark.run(order=23) - def test_web_page(self): - CommonTests.test_web_page(self, 'receive', 'Select the files you want to send, then click', True) - - @pytest.mark.run(order=24) - def test_upload_file(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test.txt') - - @pytest.mark.run(order=25) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'receive') - - @pytest.mark.run(order=26) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'receive', 1) - - @pytest.mark.run(order=27) - def test_upload_same_file_is_renamed(self): - CommonTests.test_upload_file(self, True, '/tmp/OnionShare/test-2.txt') - - @pytest.mark.run(order=28) - def test_upload_count_incremented_again(self): - CommonTests.test_counter_incremented(self, 'receive', 2) - - @pytest.mark.run(order=29) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'receive', False) - - @pytest.mark.run(order=30) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=31) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'receive', False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test.py b/tests_gui_tor/onionshare_share_mode_download_test.py deleted file mode 100644 index 2bf26690..00000000 --- a/tests_gui_tor/onionshare_share_mode_download_test.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=15) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=18) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=19) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=20) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=21) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=22) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', False) - - @pytest.mark.run(order=23) - def test_download_share(self): - CommonTests.test_download_share(self, False) - - @pytest.mark.run(order=24) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=25) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=26) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=27) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - - @pytest.mark.run(order=28) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py b/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py deleted file mode 100644 index 4792994d..00000000 --- a/tests_gui_tor/onionshare_share_mode_download_test_public_mode.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) - - @pytest.mark.run(order=20) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=21) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=22) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=23) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=24) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) - - @pytest.mark.run(order=25) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=26) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=27) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=28) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=29) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', False) - - @pytest.mark.run(order=30) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py b/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py deleted file mode 100644 index 92d52169..00000000 --- a/tests_gui_tor/onionshare_share_mode_download_test_stay_open.py +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', True) - - @pytest.mark.run(order=20) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=21) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=22) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=23) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=24) - def test_web_page(self): - CommonTests.test_web_page(self, 'share', 'Total size', True) - - @pytest.mark.run(order=25) - def test_download_share(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=26) - def test_history_widgets_present(self): - CommonTests.test_history_widgets_present(self, 'share') - - @pytest.mark.run(order=27) - def test_counter_incremented(self): - CommonTests.test_counter_incremented(self, 'share', 1) - - @pytest.mark.run(order=28) - def test_download_share_again(self): - CommonTests.test_download_share(self, True) - - @pytest.mark.run(order=29) - def test_counter_incremented_again(self): - CommonTests.test_counter_incremented(self, 'share', 2) - - @pytest.mark.run(order=30) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) - - @pytest.mark.run(order=31) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=32) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - - @pytest.mark.run(order=33) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_persistent_test.py b/tests_gui_tor/onionshare_share_mode_persistent_test.py deleted file mode 100644 index 6b9fbe16..00000000 --- a/tests_gui_tor/onionshare_share_mode_persistent_test.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - slug = '' - onion_host = '' - - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "receive_allow_receiver_shutdown": True, - "save_private_key": True, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=11) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=12) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=13) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=14) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=15) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - global slug - slug = self.gui.share_mode.server_status.web.slug - - @pytest.mark.run(order=16) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - global onion_host - onion_host = self.gui.app.onion_host - - @pytest.mark.run(order=17) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=18) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) - - @pytest.mark.run(order=19) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=20) - def test_server_status_indicator_says_closed(self): - CommonTests.test_server_status_indicator_says_closed(self, 'share', True) - - @pytest.mark.run(order=21) - def test_server_started_again(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - CommonTests.test_server_status_indicator_says_starting(self, 'share') - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=22) - def test_have_same_slug(self): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - - @pytest.mark.run(order=23) - def test_have_same_onion(self): - '''Test that we have the same onion''' - self.assertEqual(self.gui.app.onion_host, onion_host) - - @pytest.mark.run(order=24) - def test_server_is_stopped_again(self): - CommonTests.test_server_is_stopped(self, 'share', True) - CommonTests.test_web_service_is_stopped(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_stealth_test.py b/tests_gui_tor/onionshare_share_mode_stealth_test.py deleted file mode 100644 index 876efde2..00000000 --- a/tests_gui_tor/onionshare_share_mode_stealth_test.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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": True, - "use_legacy_v2_onions": False, - "use_autoupdate": True, - "version": "1.3.1" - } - testsettings = '/tmp/testsettings.json' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=20) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=21) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=22) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=23) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=24) - def test_copy_have_hidserv_auth_button(self): - CommonTests.test_copy_have_hidserv_auth_button(self, 'share') - - @pytest.mark.run(order=25) - def test_hidserv_auth_string(self): - CommonTests.test_hidserv_auth_string(self) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py deleted file mode 100644 index 37abc825..00000000 --- a/tests_gui_tor/onionshare_share_mode_tor_connection_killed_test.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=20) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=21) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=22) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=23) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=24) - def test_tor_killed_statusbar_message_shown(self): - CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') - - @pytest.mark.run(order=25) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=26) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_tor_connection_killed_test.py b/tests_gui_tor/onionshare_tor_connection_killed_test.py deleted file mode 100644 index 37abc825..00000000 --- a/tests_gui_tor/onionshare_tor_connection_killed_test.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=11) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=12) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=13) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=14) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=15) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=16) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=17) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=18) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=19) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=20) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=21) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=22) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=23) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=24) - def test_tor_killed_statusbar_message_shown(self): - CommonTests.test_tor_killed_statusbar_message_shown(self, 'share') - - @pytest.mark.run(order=25) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=26) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/run_unit_tests.sh b/tests_gui_tor/run_unit_tests.sh deleted file mode 100755 index 7d207a57..00000000 --- a/tests_gui_tor/run_unit_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^onionshare_`; do - pytest $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From d77c12a6f813a02cb36519f1c2bab9e08c20b7fb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 18:56:30 +1100 Subject: add tor marker on the stealth test --- tests_gui/onionshare_share_mode_stealth_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests_gui/onionshare_share_mode_stealth_test.py b/tests_gui/onionshare_share_mode_stealth_test.py index 56303226..9e4ffa1f 100644 --- a/tests_gui/onionshare_share_mode_stealth_test.py +++ b/tests_gui/onionshare_share_mode_stealth_test.py @@ -14,19 +14,23 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') @pytest.mark.run(order=1) + @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() @pytest.mark.run(order=2) + @pytest.mark.tor def test_run_share_mode_setup_tests(self): self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) @pytest.mark.run(order=3) + @pytest.mark.tor def test_copy_have_hidserv_auth_button(self): self.copy_have_hidserv_auth_button(self.gui.share_mode) @pytest.mark.run(order=4) + @pytest.mark.tor def test_hidserv_auth_string(self): self.hidserv_auth_string() -- cgit v1.2.3-54-g00ecf From be7bc2d839672233de4170ebc644645618f75173 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 19:01:50 +1100 Subject: cancel share test in Tor --- .../onionshare_share_mode_cancel_share_test.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests_gui/onionshare_share_mode_cancel_share_test.py diff --git a/tests_gui/onionshare_share_mode_cancel_share_test.py b/tests_gui/onionshare_share_mode_cancel_share_test.py new file mode 100644 index 00000000..62dbc8ad --- /dev/null +++ b/tests_gui/onionshare_share_mode_cancel_share_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + + @pytest.mark.run(order=3) + @pytest.mark.tor + def test_cancel_the_share(self): + self.cancel_the_share(self.gui.share_mode) + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From fd1174c41d8b625e10559b242bf0e6279a9cd20a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 12 Oct 2018 20:44:10 +1100 Subject: Commit missing test changes for canceling a share --- tests_gui/TorGuiBaseTest.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests_gui/TorGuiBaseTest.py b/tests_gui/TorGuiBaseTest.py index 1c313726..b182b1d4 100644 --- a/tests_gui/TorGuiBaseTest.py +++ b/tests_gui/TorGuiBaseTest.py @@ -132,10 +132,17 @@ class TorGuiBaseTest(GuiBaseTest): def cancel_the_share(self, mode): '''Test that we can cancel this share before it's started up ''' - QtTest.QTest.mousePress(self.gui.mode.server_status.server_button, QtCore.Qt.LeftButton) + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) QtTest.QTest.qWait(1000) - QtTest.QTest.mouseRelease(self.gui.mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.mode.server_status.status, 0) + QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 0) + self.server_is_stopped(self.gui.share_mode, False) + self.web_service_is_stopped() + # Stealth tests def copy_have_hidserv_auth_button(self, mode): -- cgit v1.2.3-54-g00ecf From 540806f0aac452001bf80da7c059d7cb9dfed9a3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 13 Oct 2018 09:49:05 +1100 Subject: add the other Tor tests into tests_gui and refactor them. Reinstate the shell script for the sake of Travis --- .travis.yml | 2 +- tests_gui/TorGuiShareTest.py | 4 +- .../onionshare_790_cancel_on_second_share_test.py | 49 +++++ tests_gui/onionshare_share_mode_timer_test.py | 27 +++ tests_gui/run_unit_tests.sh | 5 + .../onionshare_790_cancel_on_second_share_test.py | 197 --------------------- .../onionshare_share_mode_cancel_share_test.py | 149 ---------------- tests_gui_tor/onionshare_timer_test.py | 148 ---------------- 8 files changed, 84 insertions(+), 497 deletions(-) create mode 100644 tests_gui/onionshare_790_cancel_on_second_share_test.py create mode 100644 tests_gui/onionshare_share_mode_timer_test.py create mode 100755 tests_gui/run_unit_tests.sh delete mode 100644 tests_gui_tor/onionshare_790_cancel_on_second_share_test.py delete mode 100644 tests_gui_tor/onionshare_share_mode_cancel_share_test.py delete mode 100644 tests_gui_tor/onionshare_timer_test.py diff --git a/.travis.yml b/.travis.yml index c54c205d..c9bc90ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ before_script: # run CLI tests and local GUI tests script: - pytest --cov=onionshare tests/ - - xvfb-run pytest tests_gui/ -vvv + - cd tests_gui && xvfb-run ./run_unit_tests.sh diff --git a/tests_gui/TorGuiShareTest.py b/tests_gui/TorGuiShareTest.py index 8d6358c3..8e17abad 100644 --- a/tests_gui/TorGuiShareTest.py +++ b/tests_gui/TorGuiShareTest.py @@ -62,9 +62,9 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" self.run_all_share_mode_setup_tests() - self.set_timeout(self.gui.share_mode, 5) + self.set_timeout(self.gui.share_mode, 120) self.run_all_share_mode_started_tests(public_mode) self.timeout_widget_hidden(self.gui.share_mode) - self.server_timed_out(self.gui.share_mode, 10000) + self.server_timed_out(self.gui.share_mode, 125000) self.web_service_is_stopped() diff --git a/tests_gui/onionshare_790_cancel_on_second_share_test.py b/tests_gui/onionshare_790_cancel_on_second_share_test.py new file mode 100644 index 00000000..d3a1e797 --- /dev/null +++ b/tests_gui/onionshare_790_cancel_on_second_share_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +# Tests #790 regression +class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": True + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + + # Stop the share in order to test canceling the next share + #@pytest.mark.run(order=3) + #@pytest.mark.tor + #def test_stop_share(self): + # self.server_is_stopped(self.gui.share_mode, True) + # self.web_service_is_stopped() + + @pytest.mark.run(order=4) + @pytest.mark.tor + def test_cancel_the_share(self): + self.cancel_the_share(self.gui.share_mode) + + @pytest.mark.run(order=5) + @pytest.mark.tor + def test_server_is_stopped_round2(self): + self.server_is_stopped(self.gui.share_mode, False) + + @pytest.mark.run(order=6) + @pytest.mark.tor + def test_web_service_is_stopped_round2(self): + self.web_service_is_stopped() + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/onionshare_share_mode_timer_test.py b/tests_gui/onionshare_share_mode_timer_test.py new file mode 100644 index 00000000..d0b4f4cd --- /dev/null +++ b/tests_gui/onionshare_share_mode_timer_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') + + @pytest.mark.run(order=1) + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(order=2) + @pytest.mark.tor + def test_run_all_share_mode_timer_tests(self): + self.run_all_share_mode_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests_gui/run_unit_tests.sh b/tests_gui/run_unit_tests.sh new file mode 100755 index 00000000..81542749 --- /dev/null +++ b/tests_gui/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^local_`; do + pytest $test -vvv || exit 1 +done diff --git a/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py b/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py deleted file mode 100644 index 731de4fd..00000000 --- a/tests_gui_tor/onionshare_790_cancel_on_second_share_test.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_is_visible(self): - CommonTests.test_info_widget_is_visible(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=15) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_have_a_slug(self): - CommonTests.test_have_a_slug(self, 'share', False) - - @pytest.mark.run(order=18) - def test_have_an_onion(self): - CommonTests.test_have_an_onion_service(self) - - @pytest.mark.run(order=19) - def test_url_description_shown(self): - CommonTests.test_url_description_shown(self, 'share') - - @pytest.mark.run(order=20) - def test_have_copy_url_button(self): - CommonTests.test_have_copy_url_button(self, 'share') - - @pytest.mark.run(order=21) - def test_server_status_indicator_says_started(self): - CommonTests.test_server_status_indicator_says_started(self, 'share') - - @pytest.mark.run(order=22) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', True) - - @pytest.mark.run(order=23) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=24) - def test_server_working_on_start_button_pressed_round2(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=25) - def test_server_status_indicator_says_starting_round2(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=26) - def test_cancel_the_share(self): - CommonTests.test_cancel_the_share(self, 'share') - - @pytest.mark.run(order=27) - def test_server_is_stopped_round2(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=28) - def test_web_service_is_stopped_round2(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=29) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py b/tests_gui_tor/onionshare_share_mode_cancel_share_test.py deleted file mode 100644 index cdab8f85..00000000 --- a/tests_gui_tor/onionshare_share_mode_cancel_share_test.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=8) - def test_deleting_only_file_hides_delete_button(self): - CommonTests.test_deleting_only_file_hides_delete_button(self) - - @pytest.mark.run(order=9) - def test_add_a_file_and_delete_using_its_delete_widget(self): - CommonTests.test_add_a_file_and_delete_using_its_delete_widget(self) - - @pytest.mark.run(order=10) - def test_file_selection_widget_readd_files(self): - CommonTests.test_file_selection_widget_readd_files(self) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_add_delete_buttons_hidden(self): - CommonTests.test_add_delete_buttons_hidden(self) - - @pytest.mark.run(order=14) - def test_settings_button_is_hidden(self): - CommonTests.test_settings_button_is_hidden(self) - - @pytest.mark.run(order=16) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=17) - def test_cancel_the_share(self): - CommonTests.test_cancel_the_share(self, 'share') - - @pytest.mark.run(order=18) - def test_server_is_stopped(self): - CommonTests.test_server_is_stopped(self, 'share', False) - - @pytest.mark.run(order=19) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - - @pytest.mark.run(order=20) - def test_add_button_visible(self): - CommonTests.test_add_button_visible(self) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui_tor/onionshare_timer_test.py b/tests_gui_tor/onionshare_timer_test.py deleted file mode 100644 index 2b64b998..00000000 --- a/tests_gui_tor/onionshare_timer_test.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest -import pytest -import json - -from PyQt5 import QtWidgets - -from onionshare.common import Common -from onionshare.web import Web -from onionshare import onion, strings -from onionshare_gui import * - -from .commontests import CommonTests - -class OnionShareGuiTest(unittest.TestCase): - '''Test the OnionShare GUI''' - @classmethod - 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, False, 0) - - web = Web(common, False, True) - - 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, - "receive_allow_receiver_shutdown": 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' - open(testsettings, 'w').write(json.dumps(test_settings)) - - cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, False) - - @classmethod - def tearDownClass(cls): - '''Clean up after tests''' - os.remove('/tmp/test.txt') - - @pytest.mark.run(order=1) - def test_gui_loaded(self): - CommonTests.test_gui_loaded(self) - - @pytest.mark.run(order=2) - def test_windowTitle_seen(self): - CommonTests.test_windowTitle_seen(self) - - @pytest.mark.run(order=3) - def test_settings_button_is_visible(self): - CommonTests.test_settings_button_is_visible(self) - - @pytest.mark.run(order=4) - def test_server_status_bar_is_visible(self): - CommonTests.test_server_status_bar_is_visible(self) - - @pytest.mark.run(order=5) - def test_file_selection_widget_has_a_file(self): - CommonTests.test_file_selection_widget_has_a_file(self) - - @pytest.mark.run(order=6) - def test_info_widget_shows_less(self): - CommonTests.test_info_widget_shows_less(self, 'share') - - @pytest.mark.run(order=7) - def test_history_is_not_visible(self): - CommonTests.test_history_is_not_visible(self, 'share') - - @pytest.mark.run(order=8) - def test_click_toggle_history(self): - CommonTests.test_click_toggle_history(self, 'share') - - @pytest.mark.run(order=9) - def test_history_is_visible(self): - CommonTests.test_history_is_visible(self, 'share') - - @pytest.mark.run(order=10) - def test_set_timeout(self): - CommonTests.test_set_timeout(self, 'share', 120) - - @pytest.mark.run(order=11) - def test_server_working_on_start_button_pressed(self): - CommonTests.test_server_working_on_start_button_pressed(self, 'share') - - @pytest.mark.run(order=12) - def test_server_status_indicator_says_starting(self): - CommonTests.test_server_status_indicator_says_starting(self, 'share') - - @pytest.mark.run(order=13) - def test_a_server_is_started(self): - CommonTests.test_a_server_is_started(self, 'share') - - @pytest.mark.run(order=14) - def test_a_web_server_is_running(self): - CommonTests.test_a_web_server_is_running(self) - - @pytest.mark.run(order=15) - def test_timeout_widget_hidden(self): - CommonTests.test_timeout_widget_hidden(self, 'share') - - @pytest.mark.run(order=16) - def test_timeout(self): - CommonTests.test_server_timed_out(self, 'share', 125000) - - @pytest.mark.run(order=17) - def test_web_service_is_stopped(self): - CommonTests.test_web_service_is_stopped(self) - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From 1043be448339c2c343859417bcff6ab4f417674e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 13 Oct 2018 10:03:15 +1100 Subject: Another attempt at changing pytest ordering so that pytest can run without shell script --- .travis.yml | 2 +- ...al_onionshare_receive_mode_upload_public_mode_test.py | 3 +-- tests_gui/local_onionshare_receive_mode_upload_test.py | 3 +-- ...al_onionshare_share_mode_download_public_mode_test.py | 3 +-- ...ocal_onionshare_share_mode_download_stay_open_test.py | 3 +-- tests_gui/local_onionshare_share_mode_download_test.py | 3 +-- .../local_onionshare_share_mode_slug_persistent_test.py | 3 +-- tests_gui/local_onionshare_share_mode_timer_test.py | 3 +-- tests_gui/onionshare_790_cancel_on_second_share_test.py | 16 ++++------------ .../onionshare_receive_mode_upload_public_mode_test.py | 3 +-- tests_gui/onionshare_receive_mode_upload_test.py | 3 +-- tests_gui/onionshare_share_mode_cancel_share_test.py | 5 ++--- .../onionshare_share_mode_download_public_mode_test.py | 3 +-- .../onionshare_share_mode_download_stay_open_test.py | 3 +-- tests_gui/onionshare_share_mode_download_test.py | 3 +-- tests_gui/onionshare_share_mode_persistent_test.py | 3 +-- tests_gui/onionshare_share_mode_stealth_test.py | 7 +++---- tests_gui/onionshare_share_mode_timer_test.py | 3 +-- .../onionshare_share_mode_tor_connection_killed_test.py | 9 ++++----- 19 files changed, 28 insertions(+), 53 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9bc90ae..118aa147 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ before_script: # run CLI tests and local GUI tests script: - pytest --cov=onionshare tests/ - - cd tests_gui && xvfb-run ./run_unit_tests.sh + - xvfb-run pytest tests_gui/ diff --git a/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py index 1b5722c7..27bf9b2c 100644 --- a/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py @@ -13,11 +13,10 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(True, True) diff --git a/tests_gui/local_onionshare_receive_mode_upload_test.py b/tests_gui/local_onionshare_receive_mode_upload_test.py index 14a0377d..f6594105 100644 --- a/tests_gui/local_onionshare_receive_mode_upload_test.py +++ b/tests_gui/local_onionshare_receive_mode_upload_test.py @@ -12,11 +12,10 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(False, True) diff --git a/tests_gui/local_onionshare_share_mode_download_public_mode_test.py b/tests_gui/local_onionshare_share_mode_download_public_mode_test.py index 6dcb94df..2a3f9584 100644 --- a/tests_gui/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests_gui/local_onionshare_share_mode_download_public_mode_test.py @@ -12,11 +12,10 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(True, False) diff --git a/tests_gui/local_onionshare_share_mode_download_stay_open_test.py b/tests_gui/local_onionshare_share_mode_download_stay_open_test.py index 62eafff7..1f734ae7 100644 --- a/tests_gui/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests_gui/local_onionshare_share_mode_download_stay_open_test.py @@ -12,11 +12,10 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, True) diff --git a/tests_gui/local_onionshare_share_mode_download_test.py b/tests_gui/local_onionshare_share_mode_download_test.py index 98270523..274cc311 100644 --- a/tests_gui/local_onionshare_share_mode_download_test.py +++ b/tests_gui/local_onionshare_share_mode_download_test.py @@ -11,11 +11,10 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, False) diff --git a/tests_gui/local_onionshare_share_mode_slug_persistent_test.py b/tests_gui/local_onionshare_share_mode_slug_persistent_test.py index 9e171ba4..3450ec3f 100644 --- a/tests_gui/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests_gui/local_onionshare_share_mode_slug_persistent_test.py @@ -15,11 +15,10 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_share_mode_tests(self): self.run_all_share_mode_persistent_tests(False, True) diff --git a/tests_gui/local_onionshare_share_mode_timer_test.py b/tests_gui/local_onionshare_share_mode_timer_test.py index b13971b0..f9f36c48 100644 --- a/tests_gui/local_onionshare_share_mode_timer_test.py +++ b/tests_gui/local_onionshare_share_mode_timer_test.py @@ -13,11 +13,10 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') - @pytest.mark.run(order=1) def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') def test_run_all_share_mode_timer_tests(self): self.run_all_share_mode_timer_tests(False) diff --git a/tests_gui/onionshare_790_cancel_on_second_share_test.py b/tests_gui/onionshare_790_cancel_on_second_share_test.py index d3a1e797..36725fb0 100644 --- a/tests_gui/onionshare_790_cancel_on_second_share_test.py +++ b/tests_gui/onionshare_790_cancel_on_second_share_test.py @@ -13,34 +13,26 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_share_mode_tests(self): self.run_all_share_mode_tests(False, False) - # Stop the share in order to test canceling the next share - #@pytest.mark.run(order=3) - #@pytest.mark.tor - #def test_stop_share(self): - # self.server_is_stopped(self.gui.share_mode, True) - # self.web_service_is_stopped() - - @pytest.mark.run(order=4) + @pytest.mark.run(after='test_run_share_mode_tests') @pytest.mark.tor def test_cancel_the_share(self): self.cancel_the_share(self.gui.share_mode) - @pytest.mark.run(order=5) + @pytest.mark.run(after='test_cancel_the_share') @pytest.mark.tor def test_server_is_stopped_round2(self): self.server_is_stopped(self.gui.share_mode, False) - @pytest.mark.run(order=6) + @pytest.mark.run(after='test_server_is_stopped_round2') @pytest.mark.tor def test_web_service_is_stopped_round2(self): self.web_service_is_stopped() diff --git a/tests_gui/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/onionshare_receive_mode_upload_public_mode_test.py index 8561cbce..ee0c5c34 100644 --- a/tests_gui/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests_gui/onionshare_receive_mode_upload_public_mode_test.py @@ -13,12 +13,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): } cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(True, True) diff --git a/tests_gui/onionshare_receive_mode_upload_test.py b/tests_gui/onionshare_receive_mode_upload_test.py index 6dac4bc8..f1f683cc 100644 --- a/tests_gui/onionshare_receive_mode_upload_test.py +++ b/tests_gui/onionshare_receive_mode_upload_test.py @@ -12,12 +12,11 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): } cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(False, True) diff --git a/tests_gui/onionshare_share_mode_cancel_share_test.py b/tests_gui/onionshare_share_mode_cancel_share_test.py index 62dbc8ad..97adf9ce 100644 --- a/tests_gui/onionshare_share_mode_cancel_share_test.py +++ b/tests_gui/onionshare_share_mode_cancel_share_test.py @@ -11,17 +11,16 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_share_mode_setup_tests(self): self.run_all_share_mode_setup_tests() - @pytest.mark.run(order=3) + @pytest.mark.run(after='test_run_share_mode_setup_tests') @pytest.mark.tor def test_cancel_the_share(self): self.cancel_the_share(self.gui.share_mode) diff --git a/tests_gui/onionshare_share_mode_download_public_mode_test.py b/tests_gui/onionshare_share_mode_download_public_mode_test.py index 3a17b47e..a4a3bfb1 100644 --- a/tests_gui/onionshare_share_mode_download_public_mode_test.py +++ b/tests_gui/onionshare_share_mode_download_public_mode_test.py @@ -12,12 +12,11 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(True, False) diff --git a/tests_gui/onionshare_share_mode_download_stay_open_test.py b/tests_gui/onionshare_share_mode_download_stay_open_test.py index ddb513dd..33544217 100644 --- a/tests_gui/onionshare_share_mode_download_stay_open_test.py +++ b/tests_gui/onionshare_share_mode_download_stay_open_test.py @@ -12,12 +12,11 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, True) diff --git a/tests_gui/onionshare_share_mode_download_test.py b/tests_gui/onionshare_share_mode_download_test.py index 17c94215..8d6d9655 100644 --- a/tests_gui/onionshare_share_mode_download_test.py +++ b/tests_gui/onionshare_share_mode_download_test.py @@ -11,12 +11,11 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, False) diff --git a/tests_gui/onionshare_share_mode_persistent_test.py b/tests_gui/onionshare_share_mode_persistent_test.py index 415ce42e..665aecd5 100644 --- a/tests_gui/onionshare_share_mode_persistent_test.py +++ b/tests_gui/onionshare_share_mode_persistent_test.py @@ -16,12 +16,11 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_share_mode_tests(self): self.run_all_share_mode_persistent_tests(False, True) diff --git a/tests_gui/onionshare_share_mode_stealth_test.py b/tests_gui/onionshare_share_mode_stealth_test.py index 9e4ffa1f..a6bbe08e 100644 --- a/tests_gui/onionshare_share_mode_stealth_test.py +++ b/tests_gui/onionshare_share_mode_stealth_test.py @@ -13,23 +13,22 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_share_mode_setup_tests(self): self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) - @pytest.mark.run(order=3) + @pytest.mark.run(after='test_run_share_mode_setup_tests') @pytest.mark.tor def test_copy_have_hidserv_auth_button(self): self.copy_have_hidserv_auth_button(self.gui.share_mode) - @pytest.mark.run(order=4) + @pytest.mark.run(after='test_run_share_mode_setup_tests') @pytest.mark.tor def test_hidserv_auth_string(self): self.hidserv_auth_string() diff --git a/tests_gui/onionshare_share_mode_timer_test.py b/tests_gui/onionshare_share_mode_timer_test.py index d0b4f4cd..32e28c00 100644 --- a/tests_gui/onionshare_share_mode_timer_test.py +++ b/tests_gui/onionshare_share_mode_timer_test.py @@ -13,12 +13,11 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_all_share_mode_timer_tests(self): self.run_all_share_mode_timer_tests(False) diff --git a/tests_gui/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui/onionshare_share_mode_tor_connection_killed_test.py index 6ebdec97..9112aedd 100644 --- a/tests_gui/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests_gui/onionshare_share_mode_tor_connection_killed_test.py @@ -11,28 +11,27 @@ class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') - @pytest.mark.run(order=1) @pytest.mark.tor def test_run_all_common_setup_tests(self): self.run_all_common_setup_tests() - @pytest.mark.run(order=2) + @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor def test_run_share_mode_setup_tests(self): self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) - @pytest.mark.run(order=3) + @pytest.mark.run(after='test_run_share_mode_setup_tests') @pytest.mark.tor def test_tor_killed_statusbar_message_shown(self): self.tor_killed_statusbar_message_shown(self.gui.share_mode) - @pytest.mark.run(order=4) + @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') @pytest.mark.tor def test_server_is_stopped(self): self.server_is_stopped(self.gui.share_mode, False) - @pytest.mark.run(order=5) + @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') @pytest.mark.tor def test_web_service_is_stopped(self): self.web_service_is_stopped() -- cgit v1.2.3-54-g00ecf From 10cdfa7631ea98f07f081d1942c9cdb4f1caf945 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 13 Oct 2018 10:35:09 +1100 Subject: Move GUI tests into tests/ dir and fix conftest related stuff so everything loads what it needs and passes --- .travis.yml | 3 +- tests/GuiBaseTest.py | 275 +++++++++++++++++++++ tests/GuiReceiveTest.py | 45 ++++ tests/GuiShareTest.py | 168 +++++++++++++ tests/TorGuiBaseTest.py | 161 ++++++++++++ tests/TorGuiReceiveTest.py | 51 ++++ tests/TorGuiShareTest.py | 70 ++++++ tests/conftest.py | 22 +- ...onshare_receive_mode_upload_public_mode_test.py | 24 ++ tests/local_onionshare_receive_mode_upload_test.py | 23 ++ ...onshare_share_mode_download_public_mode_test.py | 23 ++ ...nionshare_share_mode_download_stay_open_test.py | 23 ++ tests/local_onionshare_share_mode_download_test.py | 22 ++ ...l_onionshare_share_mode_slug_persistent_test.py | 26 ++ tests/local_onionshare_share_mode_timer_test.py | 24 ++ .../onionshare_790_cancel_on_second_share_test.py | 41 +++ ...onshare_receive_mode_upload_public_mode_test.py | 26 ++ tests/onionshare_receive_mode_upload_test.py | 25 ++ tests/onionshare_share_mode_cancel_share_test.py | 29 +++ ...onshare_share_mode_download_public_mode_test.py | 25 ++ ...nionshare_share_mode_download_stay_open_test.py | 25 ++ tests/onionshare_share_mode_download_test.py | 24 ++ tests/onionshare_share_mode_persistent_test.py | 29 +++ tests/onionshare_share_mode_stealth_test.py | 37 +++ tests/onionshare_share_mode_timer_test.py | 26 ++ ...nshare_share_mode_tor_connection_killed_test.py | 41 +++ tests/test_onionshare_strings.py | 5 +- tests/test_onionshare_web.py | 3 +- tests_gui/GuiBaseTest.py | 275 --------------------- tests_gui/GuiReceiveTest.py | 45 ---- tests_gui/GuiShareTest.py | 168 ------------- tests_gui/TorGuiBaseTest.py | 161 ------------ tests_gui/TorGuiReceiveTest.py | 51 ---- tests_gui/TorGuiShareTest.py | 70 ------ tests_gui/__init__.py | 0 tests_gui/conftest.py | 176 ------------- ...onshare_receive_mode_upload_public_mode_test.py | 24 -- .../local_onionshare_receive_mode_upload_test.py | 23 -- ...onshare_share_mode_download_public_mode_test.py | 23 -- ...nionshare_share_mode_download_stay_open_test.py | 23 -- .../local_onionshare_share_mode_download_test.py | 22 -- ...l_onionshare_share_mode_slug_persistent_test.py | 26 -- .../local_onionshare_share_mode_timer_test.py | 24 -- .../onionshare_790_cancel_on_second_share_test.py | 41 --- ...onshare_receive_mode_upload_public_mode_test.py | 26 -- tests_gui/onionshare_receive_mode_upload_test.py | 25 -- .../onionshare_share_mode_cancel_share_test.py | 29 --- ...onshare_share_mode_download_public_mode_test.py | 25 -- ...nionshare_share_mode_download_stay_open_test.py | 25 -- tests_gui/onionshare_share_mode_download_test.py | 24 -- tests_gui/onionshare_share_mode_persistent_test.py | 29 --- tests_gui/onionshare_share_mode_stealth_test.py | 37 --- tests_gui/onionshare_share_mode_timer_test.py | 26 -- ...nshare_share_mode_tor_connection_killed_test.py | 41 --- tests_gui/run_unit_tests.sh | 5 - 55 files changed, 1288 insertions(+), 1452 deletions(-) create mode 100644 tests/GuiBaseTest.py create mode 100644 tests/GuiReceiveTest.py create mode 100644 tests/GuiShareTest.py create mode 100644 tests/TorGuiBaseTest.py create mode 100644 tests/TorGuiReceiveTest.py create mode 100644 tests/TorGuiShareTest.py create mode 100644 tests/local_onionshare_receive_mode_upload_public_mode_test.py create mode 100644 tests/local_onionshare_receive_mode_upload_test.py create mode 100644 tests/local_onionshare_share_mode_download_public_mode_test.py create mode 100644 tests/local_onionshare_share_mode_download_stay_open_test.py create mode 100644 tests/local_onionshare_share_mode_download_test.py create mode 100644 tests/local_onionshare_share_mode_slug_persistent_test.py create mode 100644 tests/local_onionshare_share_mode_timer_test.py create mode 100644 tests/onionshare_790_cancel_on_second_share_test.py create mode 100644 tests/onionshare_receive_mode_upload_public_mode_test.py create mode 100644 tests/onionshare_receive_mode_upload_test.py create mode 100644 tests/onionshare_share_mode_cancel_share_test.py create mode 100644 tests/onionshare_share_mode_download_public_mode_test.py create mode 100644 tests/onionshare_share_mode_download_stay_open_test.py create mode 100644 tests/onionshare_share_mode_download_test.py create mode 100644 tests/onionshare_share_mode_persistent_test.py create mode 100644 tests/onionshare_share_mode_stealth_test.py create mode 100644 tests/onionshare_share_mode_timer_test.py create mode 100644 tests/onionshare_share_mode_tor_connection_killed_test.py delete mode 100644 tests_gui/GuiBaseTest.py delete mode 100644 tests_gui/GuiReceiveTest.py delete mode 100644 tests_gui/GuiShareTest.py delete mode 100644 tests_gui/TorGuiBaseTest.py delete mode 100644 tests_gui/TorGuiReceiveTest.py delete mode 100644 tests_gui/TorGuiShareTest.py delete mode 100644 tests_gui/__init__.py delete mode 100644 tests_gui/conftest.py delete mode 100644 tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py delete mode 100644 tests_gui/local_onionshare_receive_mode_upload_test.py delete mode 100644 tests_gui/local_onionshare_share_mode_download_public_mode_test.py delete mode 100644 tests_gui/local_onionshare_share_mode_download_stay_open_test.py delete mode 100644 tests_gui/local_onionshare_share_mode_download_test.py delete mode 100644 tests_gui/local_onionshare_share_mode_slug_persistent_test.py delete mode 100644 tests_gui/local_onionshare_share_mode_timer_test.py delete mode 100644 tests_gui/onionshare_790_cancel_on_second_share_test.py delete mode 100644 tests_gui/onionshare_receive_mode_upload_public_mode_test.py delete mode 100644 tests_gui/onionshare_receive_mode_upload_test.py delete mode 100644 tests_gui/onionshare_share_mode_cancel_share_test.py delete mode 100644 tests_gui/onionshare_share_mode_download_public_mode_test.py delete mode 100644 tests_gui/onionshare_share_mode_download_stay_open_test.py delete mode 100644 tests_gui/onionshare_share_mode_download_test.py delete mode 100644 tests_gui/onionshare_share_mode_persistent_test.py delete mode 100644 tests_gui/onionshare_share_mode_stealth_test.py delete mode 100644 tests_gui/onionshare_share_mode_timer_test.py delete mode 100644 tests_gui/onionshare_share_mode_tor_connection_killed_test.py delete mode 100755 tests_gui/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index 118aa147..ec7ba912 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,4 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # run CLI tests and local GUI tests script: - - pytest --cov=onionshare tests/ - - xvfb-run pytest tests_gui/ + - xvfb-run pytest --cov=onionshare --cov=onionshare_gui -vvv tests/ diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py new file mode 100644 index 00000000..79543468 --- /dev/null +++ b/tests/GuiBaseTest.py @@ -0,0 +1,275 @@ +import json +import os +import requests +import shutil +import socket +import socks + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + + +class GuiBaseTest(object): + @staticmethod + def set_up(test_settings, settings_filename): + '''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) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) + return gui + + @staticmethod + def tear_down(): + try: + os.remove('/tmp/test.txt') + shutil.rmtree('/tmp/OnionShare') + except: + pass + + + def gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + + def windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + + def settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + + def server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + + def click_mode(self, mode): + '''Test that we can switch Mode by clicking the button''' + if type(mode) == ReceiveMode: + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + if type(mode) == ShareMode: + QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) + + + def click_toggle_history(self, mode): + '''Test that we can toggle Download or Upload history by clicking the toggle button''' + currently_visible = mode.history.isVisible() + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertEqual(mode.history.isVisible(), not currently_visible) + + + def history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + url = "http://127.0.0.1:{}/download".format(self.gui.app.port) + else: + url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug) + r = requests.get(url) + QtTest.QTest.qWait(2000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + + def history_is_not_visible(self, mode): + '''Test that the History section is not visible''' + self.assertFalse(mode.history.isVisible()) + + + def history_is_visible(self, mode): + '''Test that the History section is visible''' + self.assertTrue(mode.history.isVisible()) + + + def server_working_on_start_button_pressed(self, mode): + '''Test we can start the service''' + # Should be in SERVER_WORKING state + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 1) + + + def server_status_indicator_says_starting(self, mode): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) + + + def settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + + def a_server_is_started(self, mode): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + + def a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + + def have_a_slug(self, mode, public_mode): + '''Test that we have a valid slug''' + if not public_mode: + self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') + else: + self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') + + + def url_description_shown(self, mode): + '''Test that the URL label is showing''' + self.assertTrue(mode.server_status.url_description.isVisible()) + + + def have_copy_url_button(self, mode): + '''Test that the Copy URL button is shown''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + + def server_status_indicator_says_started(self, mode): + '''Test that the Server Status indicator shows we are started''' + if type(mode) == ReceiveMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + if type(mode) == ShareMode: + self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + + + def web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + + def history_widgets_present(self, mode): + '''Test that the relevant widgets are present in the history view after activity has taken place''' + self.assertFalse(mode.history.empty.isVisible()) + self.assertTrue(mode.history.not_empty.isVisible()) + + + def counter_incremented(self, mode, count): + '''Test that the counter has incremented''' + self.assertEquals(mode.history.completed_count, count) + + + def server_is_stopped(self, mode, stay_open): + '''Test that the server stops when we click Stop''' + if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): + QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(mode.server_status.status, 0) + + + def web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + + def server_status_indicator_says_closed(self, mode, stay_open): + '''Test that the Server Status indicator shows we closed''' + if type(mode) == ReceiveMode: + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + if type(mode) == ShareMode: + if stay_open: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + else: + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + def run_all_common_setup_tests(self): + self.gui_loaded() + self.windowTitle_seen() + self.settings_button_is_visible() + self.server_status_bar_is_visible() + + diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py new file mode 100644 index 00000000..1fa5c4dc --- /dev/null +++ b/tests/GuiReceiveTest.py @@ -0,0 +1,45 @@ +import os +import requests +from PyQt5 import QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiReceiveTest(GuiBaseTest): + def upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile(expected_file)) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + self.click_mode(self.gui.receive_mode) + self.history_is_not_visible(self.gui.receive_mode) + self.click_toggle_history(self.gui.receive_mode) + self.history_is_visible(self.gui.receive_mode) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.server_status_indicator_says_starting(self.gui.receive_mode) + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.receive_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.receive_mode, public_mode) + self.url_description_shown(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode) + self.server_status_indicator_says_started(self.gui.receive_mode) + self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.counter_incremented(self.gui.receive_mode, 1) + self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.counter_incremented(self.gui.receive_mode, 2) + self.history_indicator(self.gui.receive_mode, public_mode) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.a_server_is_started(self.gui.receive_mode) + self.history_indicator(self.gui.receive_mode, public_mode) + diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py new file mode 100644 index 00000000..34149dec --- /dev/null +++ b/tests/GuiShareTest.py @@ -0,0 +1,168 @@ +import socks +import zipfile +from PyQt5 import QtCore, QtTest +from .GuiBaseTest import GuiBaseTest + +class GuiShareTest(GuiBaseTest): + # Auto-stop timer tests + + def set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + + def timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + + def server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + # Persistence tests + def have_same_slug(self, slug): + '''Test that we have the same slug''' + self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) + + # Share-specific tests + + def file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + + def deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + + def file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + + def add_delete_buttons_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + + def download_share(self, public_mode): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + if public_mode: + path = '/download' + else: + path = '{}/download'.format(self.gui.share_mode.web.slug) + + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(2000) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + + + def add_button_visible(self): + '''Test that the add button should be visible''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + + def run_all_share_mode_setup_tests(self): + """Tests in share mode prior to starting a share""" + self.click_mode(self.gui.share_mode) + self.file_selection_widget_has_a_file() + self.history_is_not_visible(self.gui.share_mode) + self.click_toggle_history(self.gui.share_mode) + self.history_is_visible(self.gui.share_mode) + self.deleting_only_file_hides_delete_button() + self.add_a_file_and_delete_using_its_delete_widget() + self.file_selection_widget_readd_files() + + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.share_mode) + self.a_web_server_is_running() + self.have_a_slug(self.gui.share_mode, public_mode) + self.url_description_shown(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode) + self.server_status_indicator_says_started(self.gui.share_mode) + + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.download_share(public_mode) + self.history_widgets_present(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + self.add_button_visible() + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.a_server_is_started(self.gui.share_mode) + self.history_indicator(self.gui.share_mode, public_mode) + + + def run_all_share_mode_tests(self, public_mode, stay_open): + """End-to-end share tests""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + self.run_all_share_mode_download_tests(public_mode, stay_open) + + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): + """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + slug = self.gui.share_mode.server_status.web.slug + self.run_all_share_mode_download_tests(public_mode, stay_open) + self.have_same_slug(slug) + + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_timeout(self.gui.share_mode, 5) + self.run_all_share_mode_started_tests(public_mode) + self.timeout_widget_hidden(self.gui.share_mode) + self.server_timed_out(self.gui.share_mode, 10000) + self.web_service_is_stopped() + diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py new file mode 100644 index 00000000..b182b1d4 --- /dev/null +++ b/tests/TorGuiBaseTest.py @@ -0,0 +1,161 @@ +import json +import os +import requests +import shutil +import socket +import socks + +from PyQt5 import QtCore, QtTest + +from onionshare import strings +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.receive_mode import ReceiveMode + +from .GuiBaseTest import GuiBaseTest + +class TorGuiBaseTest(GuiBaseTest): + @staticmethod + def set_up(test_settings, settings_filename): + '''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['connection_type'] = 'automatic' + 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, False, 0) + + web = Web(common, False, False) + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), False) + return gui + + def history_indicator(self, mode, public_mode): + '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' + # Make sure history is toggled off + if mode.history.isVisible(): + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.history.isVisible()) + + # Indicator should not be visible yet + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + # Set up connecting to the onion + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + + if type(mode) == ReceiveMode: + # Upload a file + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug) + else: + path = 'http://{}/upload'.format(self.gui.app.onion_host) + response = session.post(path, files=files) + QtTest.QTest.qWait(4000) + + if type(mode) == ShareMode: + # Download files + if public_mode: + path = "http://{}/download".format(self.gui.app.onion_host) + else: + path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug) + response = session.get(path) + QtTest.QTest.qWait(4000) + + # Indicator should be visible, have a value of "1" + self.assertTrue(mode.toggle_history.indicator_label.isVisible()) + self.assertEqual(mode.toggle_history.indicator_label.text(), "1") + + # Toggle history back on, indicator should be hidden again + QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) + self.assertFalse(mode.toggle_history.indicator_label.isVisible()) + + def a_server_is_started(self, mode): + '''Test that the server has started (overriding from local tests to wait for longer)''' + QtTest.QTest.qWait(45000) + # Should now be in SERVER_STARTED state + self.assertEqual(mode.server_status.status, 2) + + def have_an_onion_service(self): + '''Test that we have a valid Onion URL''' + self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + + def web_page(self, mode, string, public_mode): + '''Test that the web page contains a string''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) + s = socks.socksocket() + s.settimeout(60) + s.connect((self.gui.app.onion_host, 80)) + if not public_mode: + path = '/{}'.format(mode.server_status.web.slug) + else: + path = '/' + http_request = 'GET {} HTTP/1.0\r\n'.format(path) + http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + f = open('/tmp/webpage') + self.assertTrue(string in f.read()) + f.close() + + def cancel_the_share(self, mode): + '''Test that we can cancel this share before it's started up ''' + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) + QtTest.QTest.qWait(1000) + QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(mode.server_status.status, 0) + self.server_is_stopped(self.gui.share_mode, False) + self.web_service_is_stopped() + + + # Stealth tests + def copy_have_hidserv_auth_button(self, mode): + '''Test that the Copy HidservAuth button is shown''' + self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) + + def hidserv_auth_string(self): + '''Test the validity of the HidservAuth string''' + self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) + + # Miscellaneous tests + def tor_killed_statusbar_message_shown(self, mode): + '''Test that the status bar message shows Tor was disconnected''' + self.gui.app.onion.cleanup(stop_tor=True) + QtTest.QTest.qWait(2500) + self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py new file mode 100644 index 00000000..67b6a811 --- /dev/null +++ b/tests/TorGuiReceiveTest.py @@ -0,0 +1,51 @@ +import os +import requests +from PyQt5 import QtTest +from .TorGuiBaseTest import TorGuiBaseTest + +class TorGuiReceiveTest(TorGuiBaseTest): + + def upload_file(self, public_mode, expected_file): + '''Test that we can upload the file''' + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) + else: + path = 'http://{}/upload'.format(self.gui.app.onion_host) + response = session.post(path, files=files) + QtTest.QTest.qWait(4000) + self.assertTrue(os.path.isfile(expected_file)) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + self.click_mode(self.gui.receive_mode) + self.history_is_not_visible(self.gui.receive_mode) + self.click_toggle_history(self.gui.receive_mode) + self.history_is_visible(self.gui.receive_mode) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.server_status_indicator_says_starting(self.gui.receive_mode) + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.receive_mode) + self.a_web_server_is_running() + self.have_an_onion_service() + self.have_a_slug(self.gui.receive_mode, public_mode) + self.url_description_shown(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode) + self.server_status_indicator_says_started(self.gui.receive_mode) + self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.history_widgets_present(self.gui.receive_mode) + self.counter_incremented(self.gui.receive_mode, 1) + self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.counter_incremented(self.gui.receive_mode, 2) + self.history_indicator(self.gui.receive_mode, public_mode) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_service_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.server_working_on_start_button_pressed(self.gui.receive_mode) + self.a_server_is_started(self.gui.receive_mode) + self.history_indicator(self.gui.receive_mode, public_mode) + diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py new file mode 100644 index 00000000..8e17abad --- /dev/null +++ b/tests/TorGuiShareTest.py @@ -0,0 +1,70 @@ +import requests +import socks +import zipfile +from PyQt5 import QtCore, QtTest +from .TorGuiBaseTest import TorGuiBaseTest +from .GuiShareTest import GuiShareTest + +class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): + def download_share(self, public_mode): + # Set up connecting to the onion + (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() + session = requests.session() + session.proxies = {} + session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) + + # Download files + if public_mode: + path = "http://{}/download".format(self.gui.app.onion_host) + else: + path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug) + response = session.get(path, stream=True) + QtTest.QTest.qWait(4000) + + if response.status_code == 200: + with open('/tmp/download.zip', 'wb') as file_to_write: + for chunk in response.iter_content(chunk_size=128): + file_to_write.write(chunk) + file_to_write.close() + zip = zipfile.ZipFile('/tmp/download.zip') + QtTest.QTest.qWait(4000) + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + # Persistence tests + def have_same_onion(self, onion): + '''Test that we have the same onion''' + self.assertEqual(self.gui.app.onion_host, onion) + + def run_all_share_mode_started_tests(self, public_mode): + """Tests in share mode after starting a share""" + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_status_indicator_says_starting(self.gui.share_mode) + self.add_delete_buttons_hidden() + self.settings_button_is_hidden() + self.a_server_is_started(self.gui.share_mode) + self.a_web_server_is_running() + self.have_an_onion_service() + self.have_a_slug(self.gui.share_mode, public_mode) + self.url_description_shown(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode) + self.server_status_indicator_says_started(self.gui.share_mode) + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): + """Same as end-to-end share tests but also test the slug is the same on multiple shared""" + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(public_mode) + slug = self.gui.share_mode.server_status.web.slug + onion = self.gui.app.onion_host + self.run_all_share_mode_download_tests(public_mode, stay_open) + self.have_same_onion(onion) + self.have_same_slug(slug) + + def run_all_share_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in share mode""" + self.run_all_share_mode_setup_tests() + self.set_timeout(self.gui.share_mode, 120) + self.run_all_share_mode_started_tests(public_mode) + self.timeout_widget_hidden(self.gui.share_mode) + self.server_timed_out(self.gui.share_mode, 125000) + self.web_service_is_stopped() + diff --git a/tests/conftest.py b/tests/conftest.py index 3ae6fd52..688b22d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,22 @@ import pytest from onionshare import common, web, settings, strings +def pytest_addoption(parser): + parser.addoption( + "--runtor", action="store_true", default=False, help="run tor tests" + ) + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--runtor"): + # --runtor given in cli: do not skip tor tests + return + skip_tor = pytest.mark.skip(reason="need --runtor option to run") + for item in items: + if "tor" in item.keywords: + item.add_marker(skip_tor) + + @pytest.fixture def temp_dir_1024(): """ Create a temporary directory that has a single file of a @@ -151,13 +167,11 @@ def time_strftime(monkeypatch): @pytest.fixture def common_obj(): - _common = common.Common() - _common.settings = settings.Settings(_common) - strings.load_strings(_common) - return _common + return common.Common() @pytest.fixture def settings_obj(sys_onionshare_dev_mode, platform_linux): _common = common.Common() _common.version = 'DUMMY_VERSION_1.2.3' + strings.load_strings(_common) return settings.Settings(_common) diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py new file mode 100644 index 00000000..27bf9b2c --- /dev/null +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..f6594105 --- /dev/null +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py new file mode 100644 index 00000000..2a3f9584 --- /dev/null +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py new file mode 100644 index 00000000..1f734ae7 --- /dev/null +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py new file mode 100644 index 00000000..274cc311 --- /dev/null +++ b/tests/local_onionshare_share_mode_download_test.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py new file mode 100644 index 00000000..3450ec3f --- /dev/null +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_persistent_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py new file mode 100644 index 00000000..f9f36c48 --- /dev/null +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') + + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + def test_run_all_share_mode_timer_tests(self): + self.run_all_share_mode_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py new file mode 100644 index 00000000..36725fb0 --- /dev/null +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +# Tests #790 regression +class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": True + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + + @pytest.mark.run(after='test_run_share_mode_tests') + @pytest.mark.tor + def test_cancel_the_share(self): + self.cancel_the_share(self.gui.share_mode) + + @pytest.mark.run(after='test_cancel_the_share') + @pytest.mark.tor + def test_server_is_stopped_round2(self): + self.server_is_stopped(self.gui.share_mode, False) + + @pytest.mark.run(after='test_server_is_stopped_round2') + @pytest.mark.tor + def test_web_service_is_stopped_round2(self): + self.web_service_is_stopped() + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py new file mode 100644 index 00000000..ee0c5c34 --- /dev/null +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiReceiveTest import TorGuiReceiveTest + +class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..f1f683cc --- /dev/null +++ b/tests/onionshare_receive_mode_upload_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiReceiveTest import TorGuiReceiveTest + +class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_receive_mode_tests(self): + self.run_all_receive_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py new file mode 100644 index 00000000..97adf9ce --- /dev/null +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + + @pytest.mark.run(after='test_run_share_mode_setup_tests') + @pytest.mark.tor + def test_cancel_the_share(self): + self.cancel_the_share(self.gui.share_mode) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py new file mode 100644 index 00000000..a4a3bfb1 --- /dev/null +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(True, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py new file mode 100644 index 00000000..33544217 --- /dev/null +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..8d6d9655 --- /dev/null +++ b/tests/onionshare_share_mode_download_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_tests(False, False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py new file mode 100644 index 00000000..665aecd5 --- /dev/null +++ b/tests/onionshare_share_mode_persistent_test.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class LocalShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "use_legacy_v2_onions": True, + "public_mode": False, + "slug": "", + "save_private_key": True, + "close_after_first_download": False, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_share_mode_tests(self): + self.run_all_share_mode_persistent_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py new file mode 100644 index 00000000..a6bbe08e --- /dev/null +++ b/tests/onionshare_share_mode_stealth_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "use_legacy_v2_onions": True, + "use_stealth": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(False) + + @pytest.mark.run(after='test_run_share_mode_setup_tests') + @pytest.mark.tor + def test_copy_have_hidserv_auth_button(self): + self.copy_have_hidserv_auth_button(self.gui.share_mode) + + @pytest.mark.run(after='test_run_share_mode_setup_tests') + @pytest.mark.tor + def test_hidserv_auth_string(self): + self.hidserv_auth_string() + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py new file mode 100644 index 00000000..32e28c00 --- /dev/null +++ b/tests/onionshare_share_mode_timer_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_all_share_mode_timer_tests(self): + self.run_all_share_mode_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py new file mode 100644 index 00000000..9112aedd --- /dev/null +++ b/tests/onionshare_share_mode_tor_connection_killed_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') + + @pytest.mark.tor + def test_run_all_common_setup_tests(self): + self.run_all_common_setup_tests() + + @pytest.mark.run(after='test_run_all_common_setup_tests') + @pytest.mark.tor + def test_run_share_mode_setup_tests(self): + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(False) + + @pytest.mark.run(after='test_run_share_mode_setup_tests') + @pytest.mark.tor + def test_tor_killed_statusbar_message_shown(self): + self.tor_killed_statusbar_message_shown(self.gui.share_mode) + + @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') + @pytest.mark.tor + def test_server_is_stopped(self): + self.server_is_stopped(self.gui.share_mode, False) + + @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') + @pytest.mark.tor + def test_web_service_is_stopped(self): + self.web_service_is_stopped() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_onionshare_strings.py b/tests/test_onionshare_strings.py index 6d39598c..ea57e3a9 100644 --- a/tests/test_onionshare_strings.py +++ b/tests/test_onionshare_strings.py @@ -23,7 +23,7 @@ import types import pytest from onionshare import strings - +from onionshare.settings import Settings # # Stub get_resource_path so it finds the correct path while running tests # def get_resource_path(filename): @@ -40,6 +40,7 @@ class TestLoadStrings: def test_load_strings_defaults_to_english( self, common_obj, locale_en, sys_onionshare_dev_mode): """ load_strings() loads English by default """ + common_obj.settings = Settings(common_obj) strings.load_strings(common_obj) assert strings._('preparing_files') == "Compressing files." @@ -47,6 +48,7 @@ class TestLoadStrings: def test_load_strings_loads_other_languages( self, common_obj, locale_fr, sys_onionshare_dev_mode): """ load_strings() loads other languages in different locales """ + common_obj.settings = Settings(common_obj) common_obj.settings.set('locale', 'fr') strings.load_strings(common_obj) assert strings._('preparing_files') == "Préparation des fichiers à partager." @@ -55,5 +57,6 @@ class TestLoadStrings: self, common_obj, locale_invalid, sys_onionshare_dev_mode): """ load_strings() raises a KeyError for an invalid locale """ with pytest.raises(KeyError): + common_obj.settings = Settings(common_obj) common_obj.settings.set('locale', 'XX') strings.load_strings(common_obj) diff --git a/tests/test_onionshare_web.py b/tests/test_onionshare_web.py index 24a0e163..d42adde4 100644 --- a/tests/test_onionshare_web.py +++ b/tests/test_onionshare_web.py @@ -31,6 +31,7 @@ import tempfile import pytest from onionshare.common import Common +from onionshare import strings from onionshare.web import Web from onionshare.settings import Settings @@ -41,7 +42,7 @@ RANDOM_STR_REGEX = re.compile(r'^[a-z2-7]+$') def web_obj(common_obj, mode, num_files=0): """ Creates a Web object, in either share mode or receive mode, ready for testing """ common_obj.load_settings() - + strings.load_strings(common_obj) web = Web(common_obj, False, mode) web.generate_slug() web.stay_open = True diff --git a/tests_gui/GuiBaseTest.py b/tests_gui/GuiBaseTest.py deleted file mode 100644 index 79543468..00000000 --- a/tests_gui/GuiBaseTest.py +++ /dev/null @@ -1,275 +0,0 @@ -import json -import os -import requests -import shutil -import socket -import socks - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - - -class GuiBaseTest(object): - @staticmethod - def set_up(test_settings, settings_filename): - '''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) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) - return gui - - @staticmethod - def tear_down(): - try: - os.remove('/tmp/test.txt') - shutil.rmtree('/tmp/OnionShare') - except: - pass - - - def gui_loaded(self): - '''Test that the GUI actually is shown''' - self.assertTrue(self.gui.show) - - - def windowTitle_seen(self): - '''Test that the window title is OnionShare''' - self.assertEqual(self.gui.windowTitle(), 'OnionShare') - - - def settings_button_is_visible(self): - '''Test that the settings button is visible''' - self.assertTrue(self.gui.settings_button.isVisible()) - - - def server_status_bar_is_visible(self): - '''Test that the status bar is visible''' - self.assertTrue(self.gui.status_bar.isVisible()) - - - def click_mode(self, mode): - '''Test that we can switch Mode by clicking the button''' - if type(mode) == ReceiveMode: - QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) - if type(mode) == ShareMode: - QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) - self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - - - def click_toggle_history(self, mode): - '''Test that we can toggle Download or Upload history by clicking the toggle button''' - currently_visible = mode.history.isVisible() - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertEqual(mode.history.isVisible(), not currently_visible) - - - def history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - url = "http://127.0.0.1:{}/download".format(self.gui.app.port) - else: - url = "http://127.0.0.1:{}/{}/download".format(self.gui.app.port, mode.web.slug) - r = requests.get(url) - QtTest.QTest.qWait(2000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - - def history_is_not_visible(self, mode): - '''Test that the History section is not visible''' - self.assertFalse(mode.history.isVisible()) - - - def history_is_visible(self, mode): - '''Test that the History section is visible''' - self.assertTrue(mode.history.isVisible()) - - - def server_working_on_start_button_pressed(self, mode): - '''Test we can start the service''' - # Should be in SERVER_WORKING state - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 1) - - - def server_status_indicator_says_starting(self, mode): - '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - - def settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) - - - def a_server_is_started(self, mode): - '''Test that the server has started''' - QtTest.QTest.qWait(2000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - - def a_web_server_is_running(self): - '''Test that the web server has started''' - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - - def have_a_slug(self, mode, public_mode): - '''Test that we have a valid slug''' - if not public_mode: - self.assertRegex(mode.server_status.web.slug, r'(\w+)-(\w+)') - else: - self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - - - def url_description_shown(self, mode): - '''Test that the URL label is showing''' - self.assertTrue(mode.server_status.url_description.isVisible()) - - - def have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' - self.assertTrue(mode.server_status.copy_url_button.isVisible()) - - - def server_status_indicator_says_started(self, mode): - '''Test that the Server Status indicator shows we are started''' - if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) - if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - - - def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - - def history_widgets_present(self, mode): - '''Test that the relevant widgets are present in the history view after activity has taken place''' - self.assertFalse(mode.history.empty.isVisible()) - self.assertTrue(mode.history.not_empty.isVisible()) - - - def counter_incremented(self, mode, count): - '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) - - - def server_is_stopped(self, mode, stay_open): - '''Test that the server stops when we click Stop''' - if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): - QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) - - - def web_service_is_stopped(self): - '''Test that the web server also stopped''' - QtTest.QTest.qWait(2000) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # We should be closed by now. Fail if not! - self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - - - def server_status_indicator_says_closed(self, mode, stay_open): - '''Test that the Server Status indicator shows we closed''' - if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) - if type(mode) == ShareMode: - if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) - else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) - - def run_all_common_setup_tests(self): - self.gui_loaded() - self.windowTitle_seen() - self.settings_button_is_visible() - self.server_status_bar_is_visible() - - diff --git a/tests_gui/GuiReceiveTest.py b/tests_gui/GuiReceiveTest.py deleted file mode 100644 index 1fa5c4dc..00000000 --- a/tests_gui/GuiReceiveTest.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import requests -from PyQt5 import QtTest -from .GuiBaseTest import GuiBaseTest - -class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) - else: - path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) - response = requests.post(path, files=files) - QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - self.click_mode(self.gui.receive_mode) - self.history_is_not_visible(self.gui.receive_mode) - self.click_toggle_history(self.gui.receive_mode) - self.history_is_visible(self.gui.receive_mode) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.server_status_indicator_says_starting(self.gui.receive_mode) - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.receive_mode) - self.a_web_server_is_running() - self.have_a_slug(self.gui.receive_mode, public_mode) - self.url_description_shown(self.gui.receive_mode) - self.have_copy_url_button(self.gui.receive_mode) - self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - self.upload_file(public_mode, '/tmp/OnionShare/test.txt') - self.history_widgets_present(self.gui.receive_mode) - self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') - self.counter_incremented(self.gui.receive_mode, 2) - self.history_indicator(self.gui.receive_mode, public_mode) - self.server_is_stopped(self.gui.receive_mode, False) - self.web_service_is_stopped() - self.server_status_indicator_says_closed(self.gui.receive_mode, False) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.a_server_is_started(self.gui.receive_mode) - self.history_indicator(self.gui.receive_mode, public_mode) - diff --git a/tests_gui/GuiShareTest.py b/tests_gui/GuiShareTest.py deleted file mode 100644 index 34149dec..00000000 --- a/tests_gui/GuiShareTest.py +++ /dev/null @@ -1,168 +0,0 @@ -import socks -import zipfile -from PyQt5 import QtCore, QtTest -from .GuiBaseTest import GuiBaseTest - -class GuiShareTest(GuiBaseTest): - # Auto-stop timer tests - - def set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - - def timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - - def server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - - # Persistence tests - def have_same_slug(self, slug): - '''Test that we have the same slug''' - self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) - - # Share-specific tests - - def file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) - - - def deleting_only_file_hides_delete_button(self): - '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' - rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) - # Delete button should be visible - self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - - def add_a_file_and_delete_using_its_delete_widget(self): - '''Test that we can also delete a file by clicking on its [X] widget''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) - - - def file_selection_widget_readd_files(self): - '''Re-add some files to the list so we can share''' - self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') - self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - - - def add_delete_buttons_hidden(self): - '''Test that the add and delete buttons are hidden when the server starts''' - self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - - - def download_share(self, public_mode): - '''Test that we can download the share''' - s = socks.socksocket() - s.settimeout(60) - s.connect(('127.0.0.1', self.gui.app.port)) - - if public_mode: - path = '/download' - else: - path = '{}/download'.format(self.gui.share_mode.web.slug) - - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: 127.0.0.1\r\n' - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - - with open('/tmp/download.zip', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(2000) - self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - - - def add_button_visible(self): - '''Test that the add button should be visible''' - self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) - - - def run_all_share_mode_setup_tests(self): - """Tests in share mode prior to starting a share""" - self.click_mode(self.gui.share_mode) - self.file_selection_widget_has_a_file() - self.history_is_not_visible(self.gui.share_mode) - self.click_toggle_history(self.gui.share_mode) - self.history_is_visible(self.gui.share_mode) - self.deleting_only_file_hides_delete_button() - self.add_a_file_and_delete_using_its_delete_widget() - self.file_selection_widget_readd_files() - - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.server_status_indicator_says_starting(self.gui.share_mode) - self.add_delete_buttons_hidden() - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.share_mode) - self.a_web_server_is_running() - self.have_a_slug(self.gui.share_mode, public_mode) - self.url_description_shown(self.gui.share_mode) - self.have_copy_url_button(self.gui.share_mode) - self.server_status_indicator_says_started(self.gui.share_mode) - - - def run_all_share_mode_download_tests(self, public_mode, stay_open): - """Tests in share mode after downloading a share""" - self.web_page(self.gui.share_mode, 'Total size', public_mode) - self.download_share(public_mode) - self.history_widgets_present(self.gui.share_mode) - self.server_is_stopped(self.gui.share_mode, stay_open) - self.web_service_is_stopped() - self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) - self.add_button_visible() - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.a_server_is_started(self.gui.share_mode) - self.history_indicator(self.gui.share_mode, public_mode) - - - def run_all_share_mode_tests(self, public_mode, stay_open): - """End-to-end share tests""" - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(public_mode) - self.run_all_share_mode_download_tests(public_mode, stay_open) - - - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): - """Same as end-to-end share tests but also test the slug is the same on multiple shared""" - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(public_mode) - slug = self.gui.share_mode.server_status.web.slug - self.run_all_share_mode_download_tests(public_mode, stay_open) - self.have_same_slug(slug) - - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - self.run_all_share_mode_setup_tests() - self.set_timeout(self.gui.share_mode, 5) - self.run_all_share_mode_started_tests(public_mode) - self.timeout_widget_hidden(self.gui.share_mode) - self.server_timed_out(self.gui.share_mode, 10000) - self.web_service_is_stopped() - diff --git a/tests_gui/TorGuiBaseTest.py b/tests_gui/TorGuiBaseTest.py deleted file mode 100644 index b182b1d4..00000000 --- a/tests_gui/TorGuiBaseTest.py +++ /dev/null @@ -1,161 +0,0 @@ -import json -import os -import requests -import shutil -import socket -import socks - -from PyQt5 import QtCore, QtTest - -from onionshare import strings -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.receive_mode import ReceiveMode - -from .GuiBaseTest import GuiBaseTest - -class TorGuiBaseTest(GuiBaseTest): - @staticmethod - def set_up(test_settings, settings_filename): - '''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['connection_type'] = 'automatic' - 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, False, 0) - - web = Web(common, False, False) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), False) - return gui - - def history_indicator(self, mode, public_mode): - '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' - # Make sure history is toggled off - if mode.history.isVisible(): - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.history.isVisible()) - - # Indicator should not be visible yet - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - # Set up connecting to the onion - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - session = requests.session() - session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - - if type(mode) == ReceiveMode: - # Upload a file - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, mode.web.slug) - else: - path = 'http://{}/upload'.format(self.gui.app.onion_host) - response = session.post(path, files=files) - QtTest.QTest.qWait(4000) - - if type(mode) == ShareMode: - # Download files - if public_mode: - path = "http://{}/download".format(self.gui.app.onion_host) - else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, mode.web.slug) - response = session.get(path) - QtTest.QTest.qWait(4000) - - # Indicator should be visible, have a value of "1" - self.assertTrue(mode.toggle_history.indicator_label.isVisible()) - self.assertEqual(mode.toggle_history.indicator_label.text(), "1") - - # Toggle history back on, indicator should be hidden again - QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) - self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - - def a_server_is_started(self, mode): - '''Test that the server has started (overriding from local tests to wait for longer)''' - QtTest.QTest.qWait(45000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - - def have_an_onion_service(self): - '''Test that we have a valid Onion URL''' - self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') - - def web_page(self, mode, string, public_mode): - '''Test that the web page contains a string''' - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - socks.set_default_proxy(socks.SOCKS5, socks_address, socks_port) - s = socks.socksocket() - s.settimeout(60) - s.connect((self.gui.app.onion_host, 80)) - if not public_mode: - path = '/{}'.format(mode.server_status.web.slug) - else: - path = '/' - http_request = 'GET {} HTTP/1.0\r\n'.format(path) - http_request += 'Host: {}\r\n'.format(self.gui.app.onion_host) - http_request += '\r\n' - s.sendall(http_request.encode('utf-8')) - with open('/tmp/webpage', 'wb') as file_to_write: - while True: - data = s.recv(1024) - if not data: - break - file_to_write.write(data) - file_to_write.close() - f = open('/tmp/webpage') - self.assertTrue(string in f.read()) - f.close() - - def cancel_the_share(self, mode): - '''Test that we can cancel this share before it's started up ''' - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.server_status_indicator_says_starting(self.gui.share_mode) - self.add_delete_buttons_hidden() - self.settings_button_is_hidden() - QtTest.QTest.mousePress(mode.server_status.server_button, QtCore.Qt.LeftButton) - QtTest.QTest.qWait(1000) - QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEqual(mode.server_status.status, 0) - self.server_is_stopped(self.gui.share_mode, False) - self.web_service_is_stopped() - - - # Stealth tests - def copy_have_hidserv_auth_button(self, mode): - '''Test that the Copy HidservAuth button is shown''' - self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) - - def hidserv_auth_string(self): - '''Test the validity of the HidservAuth string''' - self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) - - # Miscellaneous tests - def tor_killed_statusbar_message_shown(self, mode): - '''Test that the status bar message shows Tor was disconnected''' - self.gui.app.onion.cleanup(stop_tor=True) - QtTest.QTest.qWait(2500) - self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests_gui/TorGuiReceiveTest.py b/tests_gui/TorGuiReceiveTest.py deleted file mode 100644 index 67b6a811..00000000 --- a/tests_gui/TorGuiReceiveTest.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import requests -from PyQt5 import QtTest -from .TorGuiBaseTest import TorGuiBaseTest - -class TorGuiReceiveTest(TorGuiBaseTest): - - def upload_file(self, public_mode, expected_file): - '''Test that we can upload the file''' - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - session = requests.session() - session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - files = {'file[]': open('/tmp/test.txt', 'rb')} - if not public_mode: - path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) - else: - path = 'http://{}/upload'.format(self.gui.app.onion_host) - response = session.post(path, files=files) - QtTest.QTest.qWait(4000) - self.assertTrue(os.path.isfile(expected_file)) - - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): - self.click_mode(self.gui.receive_mode) - self.history_is_not_visible(self.gui.receive_mode) - self.click_toggle_history(self.gui.receive_mode) - self.history_is_visible(self.gui.receive_mode) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.server_status_indicator_says_starting(self.gui.receive_mode) - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.receive_mode) - self.a_web_server_is_running() - self.have_an_onion_service() - self.have_a_slug(self.gui.receive_mode, public_mode) - self.url_description_shown(self.gui.receive_mode) - self.have_copy_url_button(self.gui.receive_mode) - self.server_status_indicator_says_started(self.gui.receive_mode) - self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - self.upload_file(public_mode, '/tmp/OnionShare/test.txt') - self.history_widgets_present(self.gui.receive_mode) - self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') - self.counter_incremented(self.gui.receive_mode, 2) - self.history_indicator(self.gui.receive_mode, public_mode) - self.server_is_stopped(self.gui.receive_mode, False) - self.web_service_is_stopped() - self.server_status_indicator_says_closed(self.gui.receive_mode, False) - self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.a_server_is_started(self.gui.receive_mode) - self.history_indicator(self.gui.receive_mode, public_mode) - diff --git a/tests_gui/TorGuiShareTest.py b/tests_gui/TorGuiShareTest.py deleted file mode 100644 index 8e17abad..00000000 --- a/tests_gui/TorGuiShareTest.py +++ /dev/null @@ -1,70 +0,0 @@ -import requests -import socks -import zipfile -from PyQt5 import QtCore, QtTest -from .TorGuiBaseTest import TorGuiBaseTest -from .GuiShareTest import GuiShareTest - -class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): - def download_share(self, public_mode): - # Set up connecting to the onion - (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() - session = requests.session() - session.proxies = {} - session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - - # Download files - if public_mode: - path = "http://{}/download".format(self.gui.app.onion_host) - else: - path = "http://{}/{}/download".format(self.gui.app.onion_host, self.gui.share_mode.web.slug) - response = session.get(path, stream=True) - QtTest.QTest.qWait(4000) - - if response.status_code == 200: - with open('/tmp/download.zip', 'wb') as file_to_write: - for chunk in response.iter_content(chunk_size=128): - file_to_write.write(chunk) - file_to_write.close() - zip = zipfile.ZipFile('/tmp/download.zip') - QtTest.QTest.qWait(4000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) - - # Persistence tests - def have_same_onion(self, onion): - '''Test that we have the same onion''' - self.assertEqual(self.gui.app.onion_host, onion) - - def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" - self.server_working_on_start_button_pressed(self.gui.share_mode) - self.server_status_indicator_says_starting(self.gui.share_mode) - self.add_delete_buttons_hidden() - self.settings_button_is_hidden() - self.a_server_is_started(self.gui.share_mode) - self.a_web_server_is_running() - self.have_an_onion_service() - self.have_a_slug(self.gui.share_mode, public_mode) - self.url_description_shown(self.gui.share_mode) - self.have_copy_url_button(self.gui.share_mode) - self.server_status_indicator_says_started(self.gui.share_mode) - - def run_all_share_mode_persistent_tests(self, public_mode, stay_open): - """Same as end-to-end share tests but also test the slug is the same on multiple shared""" - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(public_mode) - slug = self.gui.share_mode.server_status.web.slug - onion = self.gui.app.onion_host - self.run_all_share_mode_download_tests(public_mode, stay_open) - self.have_same_onion(onion) - self.have_same_slug(slug) - - def run_all_share_mode_timer_tests(self, public_mode): - """Auto-stop timer tests in share mode""" - self.run_all_share_mode_setup_tests() - self.set_timeout(self.gui.share_mode, 120) - self.run_all_share_mode_started_tests(public_mode) - self.timeout_widget_hidden(self.gui.share_mode) - self.server_timed_out(self.gui.share_mode, 125000) - self.web_service_is_stopped() - diff --git a/tests_gui/__init__.py b/tests_gui/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests_gui/conftest.py b/tests_gui/conftest.py deleted file mode 100644 index 62108e05..00000000 --- a/tests_gui/conftest.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -# Force tests to look for resources in the source code tree -sys.onionshare_dev_mode = True - -import os -import shutil -import tempfile - -import pytest - -from onionshare import common, web, settings - -def pytest_addoption(parser): - parser.addoption( - "--runtor", action="store_true", default=False, help="run tor tests" - ) - - -def pytest_collection_modifyitems(config, items): - if config.getoption("--runtor"): - # --runtor given in cli: do not skip tor tests - return - skip_tor = pytest.mark.skip(reason="need --runtor option to run") - for item in items: - if "tor" in item.keywords: - item.add_marker(skip_tor) - - -@pytest.fixture -def temp_dir_1024(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). - """ - - tmp_dir = tempfile.mkdtemp() - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - return tmp_dir - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_dir_1024_delete(): - """ Create a temporary directory that has a single file of a - particular size (1024 bytes). The temporary directory (including - the file inside) will be deleted after fixture usage. - """ - - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) - with open(tmp_file, 'wb') as f: - f.write(b'*' * 1024) - yield tmp_dir - - -@pytest.fixture -def temp_file_1024(): - """ Create a temporary file of a particular size (1024 bytes). """ - - with tempfile.NamedTemporaryFile(delete=False) as tmp_file: - tmp_file.write(b'*' * 1024) - return tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture -def temp_file_1024_delete(): - """ - Create a temporary file of a particular size (1024 bytes). - The temporary file will be deleted after fixture usage. - """ - - with tempfile.NamedTemporaryFile() as tmp_file: - tmp_file.write(b'*' * 1024) - tmp_file.flush() - yield tmp_file.name - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def custom_zw(): - zw = web.share_mode.ZipWriter( - common.Common(), - zip_filename=common.Common.random_string(4, 6), - processed_size_callback=lambda _: 'custom_callback' - ) - yield zw - zw.close() - os.remove(zw.zip_filename) - - -# pytest > 2.9 only needs @pytest.fixture -@pytest.yield_fixture(scope='session') -def default_zw(): - zw = web.share_mode.ZipWriter(common.Common()) - yield zw - zw.close() - tmp_dir = os.path.dirname(zw.zip_filename) - shutil.rmtree(tmp_dir) - - -@pytest.fixture -def locale_en(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) - - -@pytest.fixture -def locale_fr(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) - - -@pytest.fixture -def locale_invalid(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) - - -@pytest.fixture -def locale_ru(monkeypatch): - monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) - - -@pytest.fixture -def platform_darwin(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Darwin') - - -@pytest.fixture # (scope="session") -def platform_linux(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Linux') - - -@pytest.fixture -def platform_windows(monkeypatch): - monkeypatch.setattr('platform.system', lambda: 'Windows') - - -@pytest.fixture -def sys_argv_sys_prefix(monkeypatch): - monkeypatch.setattr('sys.argv', [sys.prefix]) - - -@pytest.fixture -def sys_frozen(monkeypatch): - monkeypatch.setattr('sys.frozen', True, raising=False) - - -@pytest.fixture -def sys_meipass(monkeypatch): - monkeypatch.setattr( - 'sys._MEIPASS', os.path.expanduser('~'), raising=False) - - -@pytest.fixture # (scope="session") -def sys_onionshare_dev_mode(monkeypatch): - monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) - - -@pytest.fixture -def time_time_100(monkeypatch): - monkeypatch.setattr('time.time', lambda: 100) - - -@pytest.fixture -def time_strftime(monkeypatch): - monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') - -@pytest.fixture -def common_obj(): - return common.Common() - -@pytest.fixture -def settings_obj(sys_onionshare_dev_mode, platform_linux): - _common = common.Common() - _common.version = 'DUMMY_VERSION_1.2.3' - return settings.Settings(_common) diff --git a/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py deleted file mode 100644 index 27bf9b2c..00000000 --- a/tests_gui/local_onionshare_receive_mode_upload_public_mode_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiReceiveTest import GuiReceiveTest - -class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_receive_mode_upload_test.py b/tests_gui/local_onionshare_receive_mode_upload_test.py deleted file mode 100644 index f6594105..00000000 --- a/tests_gui/local_onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiReceiveTest import GuiReceiveTest - -class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_public_mode_test.py b/tests_gui/local_onionshare_share_mode_download_public_mode_test.py deleted file mode 100644 index 2a3f9584..00000000 --- a/tests_gui/local_onionshare_share_mode_download_public_mode_test.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_stay_open_test.py b/tests_gui/local_onionshare_share_mode_download_stay_open_test.py deleted file mode 100644 index 1f734ae7..00000000 --- a/tests_gui/local_onionshare_share_mode_download_stay_open_test.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_download_test.py b/tests_gui/local_onionshare_share_mode_download_test.py deleted file mode 100644 index 274cc311..00000000 --- a/tests_gui/local_onionshare_share_mode_download_test.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModeTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_slug_persistent_test.py b/tests_gui/local_onionshare_share_mode_slug_persistent_test.py deleted file mode 100644 index 3450ec3f..00000000 --- a/tests_gui/local_onionshare_share_mode_slug_persistent_test.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_persistent_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/local_onionshare_share_mode_timer_test.py b/tests_gui/local_onionshare_share_mode_timer_test.py deleted file mode 100644 index f9f36c48..00000000 --- a/tests_gui/local_onionshare_share_mode_timer_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .GuiShareTest import GuiShareTest - -class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') - - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_timer_tests(self): - self.run_all_share_mode_timer_tests(False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_790_cancel_on_second_share_test.py b/tests_gui/onionshare_790_cancel_on_second_share_test.py deleted file mode 100644 index 36725fb0..00000000 --- a/tests_gui/onionshare_790_cancel_on_second_share_test.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -# Tests #790 regression -class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": True - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_tests(self): - self.run_all_share_mode_tests(False, False) - - @pytest.mark.run(after='test_run_share_mode_tests') - @pytest.mark.tor - def test_cancel_the_share(self): - self.cancel_the_share(self.gui.share_mode) - - @pytest.mark.run(after='test_cancel_the_share') - @pytest.mark.tor - def test_server_is_stopped_round2(self): - self.server_is_stopped(self.gui.share_mode, False) - - @pytest.mark.run(after='test_server_is_stopped_round2') - @pytest.mark.tor - def test_web_service_is_stopped_round2(self): - self.web_service_is_stopped() - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_receive_mode_upload_public_mode_test.py b/tests_gui/onionshare_receive_mode_upload_public_mode_test.py deleted file mode 100644 index ee0c5c34..00000000 --- a/tests_gui/onionshare_receive_mode_upload_public_mode_test.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiReceiveTest import TorGuiReceiveTest - -class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_receive_mode_upload_test.py b/tests_gui/onionshare_receive_mode_upload_test.py deleted file mode 100644 index f1f683cc..00000000 --- a/tests_gui/onionshare_receive_mode_upload_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiReceiveTest import TorGuiReceiveTest - -class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "receive_allow_receiver_shutdown": True - } - cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_receive_mode_tests(self): - self.run_all_receive_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_cancel_share_test.py b/tests_gui/onionshare_share_mode_cancel_share_test.py deleted file mode 100644 index 97adf9ce..00000000 --- a/tests_gui/onionshare_share_mode_cancel_share_test.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_setup_tests(self): - self.run_all_share_mode_setup_tests() - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_cancel_the_share(self): - self.cancel_the_share(self.gui.share_mode) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_public_mode_test.py b/tests_gui/onionshare_share_mode_download_public_mode_test.py deleted file mode 100644 index a4a3bfb1..00000000 --- a/tests_gui/onionshare_share_mode_download_public_mode_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(True, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_stay_open_test.py b/tests_gui/onionshare_share_mode_download_stay_open_test.py deleted file mode 100644 index 33544217..00000000 --- a/tests_gui/onionshare_share_mode_download_stay_open_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "close_after_first_download": False, - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_download_test.py b/tests_gui/onionshare_share_mode_download_test.py deleted file mode 100644 index 8d6d9655..00000000 --- a/tests_gui/onionshare_share_mode_download_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_tests(False, False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_persistent_test.py b/tests_gui/onionshare_share_mode_persistent_test.py deleted file mode 100644 index 665aecd5..00000000 --- a/tests_gui/onionshare_share_mode_persistent_test.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class LocalShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "use_legacy_v2_onions": True, - "public_mode": False, - "slug": "", - "save_private_key": True, - "close_after_first_download": False, - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): - self.run_all_share_mode_persistent_tests(False, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_stealth_test.py b/tests_gui/onionshare_share_mode_stealth_test.py deleted file mode 100644 index a6bbe08e..00000000 --- a/tests_gui/onionshare_share_mode_stealth_test.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "use_legacy_v2_onions": True, - "use_stealth": True, - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_setup_tests(self): - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(False) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_copy_have_hidserv_auth_button(self): - self.copy_have_hidserv_auth_button(self.gui.share_mode) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_hidserv_auth_string(self): - self.hidserv_auth_string() - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_timer_test.py b/tests_gui/onionshare_share_mode_timer_test.py deleted file mode 100644 index 32e28c00..00000000 --- a/tests_gui/onionshare_share_mode_timer_test.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": False, - "shutdown_timeout": True, - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_timer_tests(self): - self.run_all_share_mode_timer_tests(False) - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/onionshare_share_mode_tor_connection_killed_test.py b/tests_gui/onionshare_share_mode_tor_connection_killed_test.py deleted file mode 100644 index 9112aedd..00000000 --- a/tests_gui/onionshare_share_mode_tor_connection_killed_test.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_setup_tests(self): - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(False) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_tor_killed_statusbar_message_shown(self): - self.tor_killed_statusbar_message_shown(self.gui.share_mode) - - @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') - @pytest.mark.tor - def test_server_is_stopped(self): - self.server_is_stopped(self.gui.share_mode, False) - - @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') - @pytest.mark.tor - def test_web_service_is_stopped(self): - self.web_service_is_stopped() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests_gui/run_unit_tests.sh b/tests_gui/run_unit_tests.sh deleted file mode 100755 index 81542749..00000000 --- a/tests_gui/run_unit_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for test in `ls -1 | egrep ^local_`; do - pytest $test -vvv || exit 1 -done -- cgit v1.2.3-54-g00ecf From 8f5ea18464824099fe7b4982e2e63eef76722a64 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 13 Oct 2018 10:39:26 +1100 Subject: Update test documentation --- BUILD.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/BUILD.md b/BUILD.md index 00d24cd2..b92f4110 100644 --- a/BUILD.md +++ b/BUILD.md @@ -143,24 +143,16 @@ OnionShare includes PyTest unit tests. To run the tests, first install some depe pip3 install -r install/requirements-tests.txt ``` -If you'd like to run the CLI-based tests that Travis runs: +Then you can run `pytest` against the `tests/` directory. ```sh pytest tests/ ``` -If you would like to run the GUI unit tests in 'local only mode': +If you would like to also run the GUI unit tests in 'tor' mode, start Tor Browser in the background, then run: ```sh -cd tests_gui_local/ -./run_unit_tests.sh -``` - -If you would like to run the GUI unit tests in 'tor' (bundled) mode: - -```sh -cd tests_gui_tor/ -./run_unit_tests.sh +pytest --runtor tests/ ``` Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive. -- cgit v1.2.3-54-g00ecf From 738be1cb4eda4fed1e6aebc1f8fb4f5c20a6e0db Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Oct 2018 09:22:09 +1100 Subject: Remove unnecessary dependencies --- tests/TorGuiBaseTest.py | 3 --- tests/TorGuiShareTest.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index b182b1d4..82ecc1b4 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -1,8 +1,5 @@ import json -import os import requests -import shutil -import socket import socks from PyQt5 import QtCore, QtTest diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index 8e17abad..aa622b4f 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -1,7 +1,6 @@ import requests -import socks import zipfile -from PyQt5 import QtCore, QtTest +from PyQt5 import QtTest from .TorGuiBaseTest import TorGuiBaseTest from .GuiShareTest import GuiShareTest -- cgit v1.2.3-54-g00ecf From e31a424a4d5ca6976fe6888b48f4171de2dc3592 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Oct 2018 14:26:22 +1100 Subject: Fix class name of Tor persistent mode test --- tests/onionshare_share_mode_persistent_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 665aecd5..9a813e5d 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -4,7 +4,7 @@ import unittest from .TorGuiShareTest import TorGuiShareTest -class LocalShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): +class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): test_settings = { -- cgit v1.2.3-54-g00ecf From 46bec2f2615cee4d15fa312188bb014be2038dd0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 14 Oct 2018 15:11:57 +1100 Subject: fix stealth test. Remove tor connection killed test, because it doesn't work like this in 'automatic' connection mode which we need for Mac/Windows testing --- tests/TorGuiBaseTest.py | 7 ---- tests/onionshare_share_mode_stealth_test.py | 10 +----- ...nshare_share_mode_tor_connection_killed_test.py | 41 ---------------------- 3 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 tests/onionshare_share_mode_tor_connection_killed_test.py diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 82ecc1b4..2fadfab7 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -149,10 +149,3 @@ class TorGuiBaseTest(GuiBaseTest): def hidserv_auth_string(self): '''Test the validity of the HidservAuth string''' self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) - - # Miscellaneous tests - def tor_killed_statusbar_message_shown(self, mode): - '''Test that the status bar message shows Tor was disconnected''' - self.gui.app.onion.cleanup(stop_tor=True) - QtTest.QTest.qWait(2500) - self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index a6bbe08e..d7e13540 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -19,18 +19,10 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): @pytest.mark.run(after='test_run_all_common_setup_tests') @pytest.mark.tor - def test_run_share_mode_setup_tests(self): + def test_run_stealth_mode_tests(self): self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_copy_have_hidserv_auth_button(self): self.copy_have_hidserv_auth_button(self.gui.share_mode) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_hidserv_auth_string(self): self.hidserv_auth_string() if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py deleted file mode 100644 index 9112aedd..00000000 --- a/tests/onionshare_share_mode_tor_connection_killed_test.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import unittest - -from .TorGuiShareTest import TorGuiShareTest - -class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') - - @pytest.mark.tor - def test_run_all_common_setup_tests(self): - self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_setup_tests(self): - self.run_all_share_mode_setup_tests() - self.run_all_share_mode_started_tests(False) - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_tor_killed_statusbar_message_shown(self): - self.tor_killed_statusbar_message_shown(self.gui.share_mode) - - @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') - @pytest.mark.tor - def test_server_is_stopped(self): - self.server_is_stopped(self.gui.share_mode, False) - - @pytest.mark.run(after='test_tor_killed_statusbar_message_shown') - @pytest.mark.tor - def test_web_service_is_stopped(self): - self.web_service_is_stopped() - - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From ed224f03884e80922127497d68576b860feaa3af Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Oct 2018 11:15:32 +1100 Subject: Move GUI tests into a single function each, which solves ordering bugs, and also means we don't need to depend on pytest-ordering --- install/requirements-tests.txt | 1 - ..._onionshare_receive_mode_upload_public_mode_test.py | 5 +---- tests/local_onionshare_receive_mode_upload_test.py | 5 +---- ..._onionshare_share_mode_download_public_mode_test.py | 5 +---- ...al_onionshare_share_mode_download_stay_open_test.py | 5 +---- tests/local_onionshare_share_mode_download_test.py | 5 +---- ...local_onionshare_share_mode_slug_persistent_test.py | 5 +---- tests/local_onionshare_share_mode_timer_test.py | 5 +---- tests/onionshare_790_cancel_on_second_share_test.py | 18 +----------------- .../onionshare_receive_mode_upload_public_mode_test.py | 6 +----- tests/onionshare_receive_mode_upload_test.py | 6 +----- tests/onionshare_share_mode_cancel_share_test.py | 10 +--------- .../onionshare_share_mode_download_public_mode_test.py | 6 +----- tests/onionshare_share_mode_download_stay_open_test.py | 6 +----- tests/onionshare_share_mode_download_test.py | 6 +----- tests/onionshare_share_mode_persistent_test.py | 6 +----- tests/onionshare_share_mode_stealth_test.py | 6 +----- tests/onionshare_share_mode_timer_test.py | 6 +----- 18 files changed, 17 insertions(+), 95 deletions(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index 0d9c1581..b931afd1 100644 --- a/install/requirements-tests.txt +++ b/install/requirements-tests.txt @@ -5,7 +5,6 @@ pluggy==0.6.0 py==1.6.0 pytest==3.4.2 pytest-faulthandler==1.5.0 -pytest-ordering==0.5 pytest-qt==3.1.0 six==1.11.0 urllib3==1.23 diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index 27bf9b2c..c99fae52 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -13,11 +13,8 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(True, True) if __name__ == "__main__": diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index f6594105..dc6c1f06 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -12,11 +12,8 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(False, True) if __name__ == "__main__": diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index 2a3f9584..bfed9443 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -12,11 +12,8 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(True, False) if __name__ == "__main__": diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index 1f734ae7..b68516a2 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -12,11 +12,8 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, True) if __name__ == "__main__": diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index 274cc311..a2a16c96 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -11,11 +11,8 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, False) if __name__ == "__main__": diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py index 3450ec3f..03285fa1 100644 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -15,11 +15,8 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_tests(self): self.run_all_share_mode_persistent_tests(False, True) if __name__ == "__main__": diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index f9f36c48..3d20efc4 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -13,11 +13,8 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - def test_run_all_share_mode_timer_tests(self): self.run_all_share_mode_timer_tests(False) if __name__ == "__main__": diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index 36725fb0..21747d4c 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -14,27 +14,11 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_tests(self): self.run_all_share_mode_tests(False, False) - - @pytest.mark.run(after='test_run_share_mode_tests') - @pytest.mark.tor - def test_cancel_the_share(self): self.cancel_the_share(self.gui.share_mode) - - @pytest.mark.run(after='test_cancel_the_share') - @pytest.mark.tor - def test_server_is_stopped_round2(self): self.server_is_stopped(self.gui.share_mode, False) - - @pytest.mark.run(after='test_server_is_stopped_round2') - @pytest.mark.tor - def test_web_service_is_stopped_round2(self): self.web_service_is_stopped() if __name__ == "__main__": diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py index ee0c5c34..56b49c81 100644 --- a/tests/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -14,12 +14,8 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(True, True) if __name__ == "__main__": diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py index f1f683cc..0fd8d5ed 100644 --- a/tests/onionshare_receive_mode_upload_test.py +++ b/tests/onionshare_receive_mode_upload_test.py @@ -13,12 +13,8 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_receive_mode_tests(self): self.run_all_receive_mode_tests(False, True) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index 97adf9ce..9770cc35 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -12,17 +12,9 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_share_mode_setup_tests(self): self.run_all_share_mode_setup_tests() - - @pytest.mark.run(after='test_run_share_mode_setup_tests') - @pytest.mark.tor - def test_cancel_the_share(self): self.cancel_the_share(self.gui.share_mode) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py index a4a3bfb1..8409720a 100644 --- a/tests/onionshare_share_mode_download_public_mode_test.py +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -13,12 +13,8 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(True, False) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py index 33544217..c16f91e9 100644 --- a/tests/onionshare_share_mode_download_stay_open_test.py +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -13,12 +13,8 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, True) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py index 8d6d9655..194328d9 100644 --- a/tests/onionshare_share_mode_download_test.py +++ b/tests/onionshare_share_mode_download_test.py @@ -12,12 +12,8 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): self.run_all_share_mode_tests(False, False) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 9a813e5d..3c283943 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -17,12 +17,8 @@ class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_tests(self): self.run_all_share_mode_persistent_tests(False, True) if __name__ == "__main__": diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index d7e13540..b414de1f 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -14,12 +14,8 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_stealth_mode_tests(self): self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) self.copy_have_hidserv_auth_button(self.gui.share_mode) diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py index 32e28c00..b53905d0 100644 --- a/tests/onionshare_share_mode_timer_test.py +++ b/tests/onionshare_share_mode_timer_test.py @@ -14,12 +14,8 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') @pytest.mark.tor - def test_run_all_common_setup_tests(self): + def test_gui(self): self.run_all_common_setup_tests() - - @pytest.mark.run(after='test_run_all_common_setup_tests') - @pytest.mark.tor - def test_run_all_share_mode_timer_tests(self): self.run_all_share_mode_timer_tests(False) if __name__ == "__main__": -- cgit v1.2.3-54-g00ecf From 325980eedec470992a3b73831bb9f9d741b2a4ea Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 15 Oct 2018 17:33:21 +1100 Subject: Remove second arg from two calls to strings() --- onionshare_gui/mode/share_mode/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 62c5d8a7..436d42f7 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -284,7 +284,7 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True)) + self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) def on_reload_settings(self): """ -- cgit v1.2.3-54-g00ecf From cc4958908028b885729234931d2df5a6ad12f2b4 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 16 Oct 2018 13:01:44 +1100 Subject: More coverage such as 404 ratelimit, large file tests. Standardise some method naming conventions and other fixes/cleanup --- tests/GuiBaseTest.py | 80 ++++++++++++----- tests/GuiReceiveTest.py | 65 ++++++++++++-- tests/GuiShareTest.py | 99 ++++++++++++++-------- tests/TorGuiBaseTest.py | 31 +++++-- tests/TorGuiReceiveTest.py | 14 +-- tests/TorGuiShareTest.py | 31 +++++-- ...onshare_404_public_mode_skips_ratelimit_test.py | 21 +++++ ...local_onionshare_404_triggers_ratelimit_test.py | 20 +++++ ...l_onionshare_receive_mode_sender_closed_test.py | 20 +++++ tests/local_onionshare_receive_mode_timer_test.py | 20 +++++ ...re_receive_mode_upload_non_writable_dir_test.py | 19 +++++ ...onshare_receive_mode_upload_public_mode_test.py | 1 - tests/local_onionshare_receive_mode_upload_test.py | 1 - ...onshare_share_mode_download_public_mode_test.py | 1 - ...nionshare_share_mode_download_stay_open_test.py | 1 - tests/local_onionshare_share_mode_download_test.py | 1 - ...al_onionshare_share_mode_large_download_test.py | 18 ++++ ...l_onionshare_share_mode_slug_persistent_test.py | 1 - tests/local_onionshare_share_mode_timer_test.py | 1 - .../onionshare_790_cancel_on_second_share_test.py | 2 +- 20 files changed, 360 insertions(+), 87 deletions(-) create mode 100644 tests/local_onionshare_404_public_mode_skips_ratelimit_test.py create mode 100644 tests/local_onionshare_404_triggers_ratelimit_test.py create mode 100644 tests/local_onionshare_receive_mode_sender_closed_test.py create mode 100644 tests/local_onionshare_receive_mode_timer_test.py create mode 100644 tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py create mode 100644 tests/local_onionshare_share_mode_large_download_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 79543468..e2f194db 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -26,6 +26,13 @@ class GuiBaseTest(object): testfile.write('onionshare') testfile.close() + # Create a test dir and files + if not os.path.exists('/tmp/testdir'): + testdir = os.mkdir('/tmp/testdir') + testfile = open('/tmp/testdir/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() common.settings = Settings(common) common.define_css() @@ -46,14 +53,17 @@ class GuiBaseTest(object): web = Web(common, False, True) open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), True) + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/{}.json'.format(settings_filename), True) return gui @staticmethod def tear_down(): + '''Clean up after tests''' try: os.remove('/tmp/test.txt') + os.remove('/tmp/largefile') shutil.rmtree('/tmp/OnionShare') + shutil.rmtree('/tmp/testdir') except: pass @@ -72,6 +82,11 @@ class GuiBaseTest(object): '''Test that the settings button is visible''' self.assertTrue(self.gui.settings_button.isVisible()) + + def settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + def server_status_bar_is_visible(self): '''Test that the status bar is visible''' @@ -152,22 +167,17 @@ class GuiBaseTest(object): def server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - - - def settings_button_is_hidden(self): - '''Test that the settings button is hidden when the server starts''' - self.assertFalse(self.gui.settings_button.isVisible()) + self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - def a_server_is_started(self, mode): + def server_is_started(self, mode, startup_time=2000): '''Test that the server has started''' - QtTest.QTest.qWait(2000) + QtTest.QTest.qWait(startup_time) # Should now be in SERVER_STARTED state self.assertEqual(mode.server_status.status, 2) - def a_web_server_is_running(self): + def web_server_is_running(self): '''Test that the web server has started''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -187,17 +197,24 @@ class GuiBaseTest(object): self.assertTrue(mode.server_status.url_description.isVisible()) - def have_copy_url_button(self, mode): - '''Test that the Copy URL button is shown''' + def have_copy_url_button(self, mode, public_mode): + '''Test that the Copy URL button is shown and that the clipboard is correct''' self.assertTrue(mode.server_status.copy_url_button.isVisible()) + QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + clipboard = self.gui.qtapp.clipboard() + if public_mode: + self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}'.format(self.gui.app.port)) + else: + self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug)) + def server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if type(mode) == ReceiveMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) + self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_receive_started')) if type(mode) == ShareMode: - self.assertEquals(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) + self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) def web_page(self, mode, string, public_mode): @@ -237,17 +254,17 @@ class GuiBaseTest(object): def counter_incremented(self, mode, count): '''Test that the counter has incremented''' - self.assertEquals(mode.history.completed_count, count) + self.assertEqual(mode.history.completed_count, count) def server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) - self.assertEquals(mode.server_status.status, 0) + self.assertEqual(mode.server_status.status, 0) - def web_service_is_stopped(self): + def web_server_is_stopped(self): '''Test that the web server also stopped''' QtTest.QTest.qWait(2000) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -259,12 +276,35 @@ class GuiBaseTest(object): def server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if type(mode) == ReceiveMode: - self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) + self.assertEqual(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped')) if type(mode) == ShareMode: if stay_open: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_stopped')) else: - self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + self.assertEqual(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically')) + + + # Auto-stop timer tests + def set_timeout(self, mode, timeout): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) + mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) + + + def timeout_widget_hidden(self, mode): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) + + + def server_timed_out(self, mode, wait): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(wait) + # We should have timed out now + self.assertEqual(mode.server_status.status, 0) + + + # 'Grouped' tests follow from here def run_all_common_setup_tests(self): self.gui_loaded() diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 1fa5c4dc..84d6a55a 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -15,7 +15,39 @@ class GuiReceiveTest(GuiBaseTest): QtTest.QTest.qWait(2000) self.assertTrue(os.path.isfile(expected_file)) - def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + def upload_file_should_fail(self, public_mode, expected_file): + '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + response = requests.post(path, files=files) + + # A nasty hack to avoid the Alert dialog that blocks the rest of the test + self.gui.qtapp.exit() + self.assertTrue('Error uploading, please inform the OnionShare user' in response.text) + + def upload_dir_permissions(self, mode=0o755): + '''Manipulate the permissions on the upload dir in between tests''' + os.chmod('/tmp/OnionShare', mode) + + def run_receive_mode_sender_closed_tests(self, public_mode): + '''Test that the share can be stopped by the sender in receive mode''' + if not public_mode: + path = 'http://127.0.0.1:{}/{}/close'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/close'.format(self.gui.app.port) + response = requests.post(path) + self.server_is_stopped(self.gui.receive_mode, False) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + + + # 'Grouped' tests follow from here + + def run_all_receive_mode_setup_tests(self, public_mode): + '''Set up a share in Receive mode and start it''' self.click_mode(self.gui.receive_mode) self.history_is_not_visible(self.gui.receive_mode) self.click_toggle_history(self.gui.receive_mode) @@ -23,13 +55,17 @@ class GuiReceiveTest(GuiBaseTest): self.server_working_on_start_button_pressed(self.gui.receive_mode) self.server_status_indicator_says_starting(self.gui.receive_mode) self.settings_button_is_hidden() - self.a_server_is_started(self.gui.receive_mode) - self.a_web_server_is_running() + self.server_is_started(self.gui.receive_mode) + self.web_server_is_running() self.have_a_slug(self.gui.receive_mode, public_mode) self.url_description_shown(self.gui.receive_mode) - self.have_copy_url_button(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) + + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + '''Upload files in receive mode and stop the share''' + self.run_all_receive_mode_setup_tests(public_mode) self.upload_file(public_mode, '/tmp/OnionShare/test.txt') self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) @@ -37,9 +73,26 @@ class GuiReceiveTest(GuiBaseTest): self.counter_incremented(self.gui.receive_mode, 2) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) - self.web_service_is_stopped() + self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.receive_mode, False) self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.a_server_is_started(self.gui.receive_mode) + self.server_is_started(self.gui.receive_mode) self.history_indicator(self.gui.receive_mode, public_mode) + def run_all_receive_mode_unwritable_dir_tests(self, public_mode, receive_allow_receiver_shutdown): + '''Attempt to upload (unwritable) files in receive mode and stop the share''' + self.run_all_receive_mode_setup_tests(public_mode) + self.upload_dir_permissions(0o400) + self.upload_file_should_fail(public_mode, '/tmp/OnionShare/test.txt') + self.server_is_stopped(self.gui.receive_mode, True) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.receive_mode, False) + self.upload_dir_permissions(0o755) + + def run_all_receive_mode_timer_tests(self, public_mode): + """Auto-stop timer tests in receive mode""" + self.run_all_receive_mode_setup_tests(public_mode) + self.set_timeout(self.gui.receive_mode, 5) + self.timeout_widget_hidden(self.gui.receive_mode) + self.server_timed_out(self.gui.receive_mode, 15000) + self.web_server_is_stopped() diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 34149dec..4f2f58e7 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -1,29 +1,11 @@ +import os +import requests import socks import zipfile from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest class GuiShareTest(GuiBaseTest): - # Auto-stop timer tests - - def set_timeout(self, mode, timeout): - '''Test that the timeout can be set''' - timer = QtCore.QDateTime.currentDateTime().addSecs(timeout) - mode.server_status.shutdown_timeout.setDateTime(timer) - self.assertTrue(mode.server_status.shutdown_timeout.dateTime(), timer) - - - def timeout_widget_hidden(self, mode): - '''Test that the timeout widget is hidden when share has started''' - self.assertFalse(mode.server_status.shutdown_timeout_container.isVisible()) - - - def server_timed_out(self, mode, wait): - '''Test that the server has timed out after the timer ran out''' - QtTest.QTest.qWait(wait) - # We should have timed out now - self.assertEqual(mode.server_status.status, 0) - # Persistence tests def have_same_slug(self, slug): '''Test that we have the same slug''' @@ -31,19 +13,26 @@ class GuiShareTest(GuiBaseTest): # Share-specific tests - def file_selection_widget_has_a_file(self): - '''Test that the number of files in the list is 1''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + def file_selection_widget_has_files(self): + '''Test that the number of items in the list is 2''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) - def deleting_only_file_hides_delete_button(self): + def deleting_all_files_hides_delete_button(self): '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) # Delete button should be visible self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) - # Click delete, and since there's no more files, the delete button should be hidden + # Click delete, delete button should still be visible since we have one more file QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + + # No more files, the delete button should be hidden self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) @@ -60,7 +49,15 @@ class GuiShareTest(GuiBaseTest): self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def add_large_file(self): + '''Add a large file to the share''' + size = 1024*1024*155 + with open('/tmp/large_file', 'wb') as fout: + fout.write(os.urandom(size)) + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/large_file') + def add_delete_buttons_hidden(self): '''Test that the add and delete buttons are hidden when the server starts''' self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) @@ -95,35 +92,56 @@ class GuiShareTest(GuiBaseTest): QtTest.QTest.qWait(2000) self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) - + def hit_404(self, public_mode): + '''Test that the server stops after too many 404s, or doesn't when in public_mode''' + bogus_path = '/gimme' + url = "http://127.0.0.1:{}/{}".format(self.gui.app.port, bogus_path) + + for _ in range(20): + r = requests.get(url) + + # A nasty hack to avoid the Alert dialog that blocks the rest of the test + if not public_mode: + self.gui.qtapp.exit() + + # In public mode, we should still be running (no rate-limiting) + if public_mode: + self.web_server_is_running() + # In non-public mode, we should be shut down (rate-limiting) + else: + self.web_server_is_stopped() + + def add_button_visible(self): '''Test that the add button should be visible''' self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + # 'Grouped' tests follow from here + def run_all_share_mode_setup_tests(self): """Tests in share mode prior to starting a share""" self.click_mode(self.gui.share_mode) - self.file_selection_widget_has_a_file() + self.file_selection_widget_has_files() self.history_is_not_visible(self.gui.share_mode) self.click_toggle_history(self.gui.share_mode) self.history_is_visible(self.gui.share_mode) - self.deleting_only_file_hides_delete_button() + self.deleting_all_files_hides_delete_button() self.add_a_file_and_delete_using_its_delete_widget() self.file_selection_widget_readd_files() - def run_all_share_mode_started_tests(self, public_mode): + def run_all_share_mode_started_tests(self, public_mode, startup_time=2000): """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_status_indicator_says_starting(self.gui.share_mode) self.add_delete_buttons_hidden() self.settings_button_is_hidden() - self.a_server_is_started(self.gui.share_mode) - self.a_web_server_is_running() + self.server_is_started(self.gui.share_mode, startup_time) + self.web_server_is_running() self.have_a_slug(self.gui.share_mode, public_mode) self.url_description_shown(self.gui.share_mode) - self.have_copy_url_button(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) @@ -133,11 +151,11 @@ class GuiShareTest(GuiBaseTest): self.download_share(public_mode) self.history_widgets_present(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, stay_open) - self.web_service_is_stopped() + self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) self.add_button_visible() self.server_working_on_start_button_pressed(self.gui.share_mode) - self.a_server_is_started(self.gui.share_mode) + self.server_is_started(self.gui.share_mode) self.history_indicator(self.gui.share_mode, public_mode) @@ -148,6 +166,17 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_download_tests(public_mode, stay_open) + def run_all_large_file_tests(self, public_mode, stay_open): + """Same as above but with a larger file""" + self.run_all_share_mode_setup_tests() + self.add_large_file() + self.run_all_share_mode_started_tests(public_mode, startup_time=15000) + self.assertTrue(self.gui.share_mode.filesize_warning.isVisible()) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): """Same as end-to-end share tests but also test the slug is the same on multiple shared""" self.run_all_share_mode_setup_tests() @@ -164,5 +193,5 @@ class GuiShareTest(GuiBaseTest): self.run_all_share_mode_started_tests(public_mode) self.timeout_widget_hidden(self.gui.share_mode) self.server_timed_out(self.gui.share_mode, 10000) - self.web_service_is_stopped() + self.web_server_is_stopped() diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 2fadfab7..aee05096 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -1,4 +1,5 @@ import json +import os import requests import socks @@ -24,6 +25,13 @@ class TorGuiBaseTest(GuiBaseTest): testfile.write('onionshare') testfile.close() + # Create a test dir and files + if not os.path.exists('/tmp/testdir'): + testdir = os.mkdir('/tmp/testdir') + testfile = open('/tmp/testdir/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() common.settings = Settings(common) common.define_css() @@ -45,7 +53,7 @@ class TorGuiBaseTest(GuiBaseTest): web = Web(common, False, False) open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], '/tmp/{}.json'.format(settings_filename), False) + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/{}.json'.format(settings_filename), False) return gui def history_indicator(self, mode, public_mode): @@ -91,12 +99,6 @@ class TorGuiBaseTest(GuiBaseTest): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - def a_server_is_started(self, mode): - '''Test that the server has started (overriding from local tests to wait for longer)''' - QtTest.QTest.qWait(45000) - # Should now be in SERVER_STARTED state - self.assertEqual(mode.server_status.status, 2) - def have_an_onion_service(self): '''Test that we have a valid Onion URL''' self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') @@ -127,6 +129,17 @@ class TorGuiBaseTest(GuiBaseTest): self.assertTrue(string in f.read()) f.close() + def have_copy_url_button(self, mode, public_mode): + '''Test that the Copy URL button is shown and that the clipboard is correct''' + self.assertTrue(mode.server_status.copy_url_button.isVisible()) + + QtTest.QTest.mouseClick(mode.server_status.copy_url_button, QtCore.Qt.LeftButton) + clipboard = self.gui.qtapp.clipboard() + if public_mode: + self.assertEqual(clipboard.text(), 'http://{}'.format(self.gui.app.onion_host)) + else: + self.assertEqual(clipboard.text(), 'http://{}/{}'.format(self.gui.app.onion_host, mode.server_status.web.slug)) + def cancel_the_share(self, mode): '''Test that we can cancel this share before it's started up ''' self.server_working_on_start_button_pressed(self.gui.share_mode) @@ -138,13 +151,15 @@ class TorGuiBaseTest(GuiBaseTest): QtTest.QTest.mouseRelease(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 0) self.server_is_stopped(self.gui.share_mode, False) - self.web_service_is_stopped() + self.web_server_is_stopped() # Stealth tests def copy_have_hidserv_auth_button(self, mode): '''Test that the Copy HidservAuth button is shown''' self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) + clipboard = self.gui.qtapp.clipboard() + self.assertRegex(clipboard.text(), r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) def hidserv_auth_string(self): '''Test the validity of the HidservAuth string''' diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py index 67b6a811..3c380b8a 100644 --- a/tests/TorGuiReceiveTest.py +++ b/tests/TorGuiReceiveTest.py @@ -20,7 +20,11 @@ class TorGuiReceiveTest(TorGuiBaseTest): QtTest.QTest.qWait(4000) self.assertTrue(os.path.isfile(expected_file)) + + # 'Grouped' tests follow from here + def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): + '''Run a full suite of tests in Receive mode''' self.click_mode(self.gui.receive_mode) self.history_is_not_visible(self.gui.receive_mode) self.click_toggle_history(self.gui.receive_mode) @@ -28,12 +32,12 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.server_working_on_start_button_pressed(self.gui.receive_mode) self.server_status_indicator_says_starting(self.gui.receive_mode) self.settings_button_is_hidden() - self.a_server_is_started(self.gui.receive_mode) - self.a_web_server_is_running() + self.server_is_started(self.gui.receive_mode, startup_time=45000) + self.web_server_is_running() self.have_an_onion_service() self.have_a_slug(self.gui.receive_mode, public_mode) self.url_description_shown(self.gui.receive_mode) - self.have_copy_url_button(self.gui.receive_mode) + self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) self.upload_file(public_mode, '/tmp/OnionShare/test.txt') @@ -43,9 +47,9 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.counter_incremented(self.gui.receive_mode, 2) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) - self.web_service_is_stopped() + self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.receive_mode, False) self.server_working_on_start_button_pressed(self.gui.receive_mode) - self.a_server_is_started(self.gui.receive_mode) + self.server_is_started(self.gui.receive_mode, startup_time=45000) self.history_indicator(self.gui.receive_mode, public_mode) diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index aa622b4f..ff8d0bb7 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -6,6 +6,7 @@ from .GuiShareTest import GuiShareTest class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): def download_share(self, public_mode): + '''Test downloading a share''' # Set up connecting to the onion (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() session = requests.session() @@ -27,27 +28,46 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): file_to_write.close() zip = zipfile.ZipFile('/tmp/download.zip') QtTest.QTest.qWait(4000) - self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + self.assertEqual('onionshare', zip.read('test.txt').decode('utf-8')) + # Persistence tests def have_same_onion(self, onion): '''Test that we have the same onion''' self.assertEqual(self.gui.app.onion_host, onion) + + # 'Grouped' tests follow from here + def run_all_share_mode_started_tests(self, public_mode): """Tests in share mode after starting a share""" self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_status_indicator_says_starting(self.gui.share_mode) self.add_delete_buttons_hidden() self.settings_button_is_hidden() - self.a_server_is_started(self.gui.share_mode) - self.a_web_server_is_running() + self.server_is_started(self.gui.share_mode, startup_time=45000) + self.web_server_is_running() self.have_an_onion_service() self.have_a_slug(self.gui.share_mode, public_mode) self.url_description_shown(self.gui.share_mode) - self.have_copy_url_button(self.gui.share_mode) + self.have_copy_url_button(self.gui.share_mode, public_mode) self.server_status_indicator_says_started(self.gui.share_mode) + + def run_all_share_mode_download_tests(self, public_mode, stay_open): + """Tests in share mode after downloading a share""" + self.web_page(self.gui.share_mode, 'Total size', public_mode) + self.download_share(public_mode) + self.history_widgets_present(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, stay_open) + self.web_server_is_stopped() + self.server_status_indicator_says_closed(self.gui.share_mode, stay_open) + self.add_button_visible() + self.server_working_on_start_button_pressed(self.gui.share_mode) + self.server_is_started(self.gui.share_mode, startup_time=45000) + self.history_indicator(self.gui.share_mode, public_mode) + + def run_all_share_mode_persistent_tests(self, public_mode, stay_open): """Same as end-to-end share tests but also test the slug is the same on multiple shared""" self.run_all_share_mode_setup_tests() @@ -57,6 +77,7 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.run_all_share_mode_download_tests(public_mode, stay_open) self.have_same_onion(onion) self.have_same_slug(slug) + def run_all_share_mode_timer_tests(self, public_mode): """Auto-stop timer tests in share mode""" @@ -65,5 +86,5 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): self.run_all_share_mode_started_tests(public_mode) self.timeout_widget_hidden(self.gui.share_mode) self.server_timed_out(self.gui.share_mode, 125000) - self.web_service_is_stopped() + self.web_server_is_stopped() diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py new file mode 100644 index 00000000..e4e0a03f --- /dev/null +++ b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiShareTest import GuiShareTest + +class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False, + "public_mode": True + } + cls.gui = GuiShareTest.set_up(test_settings, 'Local404PublicModeRateLimitTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_tests(True, True) + self.hit_404(True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py new file mode 100644 index 00000000..ce26eae1 --- /dev/null +++ b/tests/local_onionshare_404_triggers_ratelimit_test.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiShareTest import GuiShareTest + +class Local404RateLimitTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False + } + cls.gui = GuiShareTest.set_up(test_settings, 'Local404RateLimitTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_tests(False, True) + self.hit_404(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py new file mode 100644 index 00000000..6960301a --- /dev/null +++ b/tests/local_onionshare_receive_mode_sender_closed_test.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_receive_mode_tests(False, True) + self.run_receive_mode_sender_closed_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py new file mode 100644 index 00000000..1784ba94 --- /dev/null +++ b/tests/local_onionshare_receive_mode_timer_test.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTimerTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_receive_mode_timer_tests(False) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py new file mode 100644 index 00000000..e33a6461 --- /dev/null +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_receive_mode_unwritable_dir_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index c99fae52..2c7cbd5e 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiReceiveTest import GuiReceiveTest diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index dc6c1f06..fb5d36a7 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiReceiveTest import GuiReceiveTest diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index bfed9443..3b4ad0e8 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiShareTest import GuiShareTest diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index b68516a2..dfe7f56c 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiShareTest import GuiShareTest diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index a2a16c96..6b6b0bcc 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiShareTest import GuiShareTest diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py new file mode 100644 index 00000000..6fe77752 --- /dev/null +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_large_file_tests(False, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py index 03285fa1..a28f5a23 100644 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiShareTest import GuiShareTest diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index 3d20efc4..9e3b0b5e 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from .GuiShareTest import GuiShareTest diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index 21747d4c..327a8456 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -19,7 +19,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): self.run_all_share_mode_tests(False, False) self.cancel_the_share(self.gui.share_mode) self.server_is_stopped(self.gui.share_mode, False) - self.web_service_is_stopped() + self.web_server_is_stopped() if __name__ == "__main__": unittest.main() -- cgit v1.2.3-54-g00ecf From fea34c0f34c6bf543a4c5b3295dd8c953f001037 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 16 Oct 2018 15:53:35 +1100 Subject: Add Settings GUI test --- tests/SettingsGuiBaseTest.py | 49 ++++++ ...onshare_404_public_mode_skips_ratelimit_test.py | 4 + ...local_onionshare_404_triggers_ratelimit_test.py | 4 + ...l_onionshare_receive_mode_sender_closed_test.py | 4 + tests/local_onionshare_receive_mode_timer_test.py | 4 + ...re_receive_mode_upload_non_writable_dir_test.py | 4 + ...onshare_receive_mode_upload_public_mode_test.py | 4 + tests/local_onionshare_receive_mode_upload_test.py | 4 + tests/local_onionshare_settings_dialog_test.py | 173 +++++++++++++++++++++ ...onshare_share_mode_download_public_mode_test.py | 4 + ...nionshare_share_mode_download_stay_open_test.py | 4 + tests/local_onionshare_share_mode_download_test.py | 4 + ...al_onionshare_share_mode_large_download_test.py | 4 + ...l_onionshare_share_mode_slug_persistent_test.py | 4 + tests/local_onionshare_share_mode_timer_test.py | 4 + .../onionshare_790_cancel_on_second_share_test.py | 4 + ...onshare_receive_mode_upload_public_mode_test.py | 4 + tests/onionshare_receive_mode_upload_test.py | 4 + tests/onionshare_share_mode_cancel_share_test.py | 4 + ...onshare_share_mode_download_public_mode_test.py | 4 + ...nionshare_share_mode_download_stay_open_test.py | 4 + tests/onionshare_share_mode_download_test.py | 4 + tests/onionshare_share_mode_persistent_test.py | 4 + tests/onionshare_share_mode_stealth_test.py | 4 + tests/onionshare_share_mode_timer_test.py | 4 + 25 files changed, 314 insertions(+) create mode 100644 tests/SettingsGuiBaseTest.py create mode 100644 tests/local_onionshare_settings_dialog_test.py diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py new file mode 100644 index 00000000..dac074fc --- /dev/null +++ b/tests/SettingsGuiBaseTest.py @@ -0,0 +1,49 @@ +import json +import os +import sys +from PyQt5 import QtWidgets + +from onionshare import strings +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 +from onionshare_gui.settings_dialog import SettingsDialog + +class SettingsGuiBaseTest(object): + @staticmethod + def set_up(test_settings, settings_filename): + '''Create the GUI''' + # 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) + + # Start the Onion + testonion = Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + for key, val in common.settings.default_settings.items(): + if key not in test_settings: + test_settings[key] = val + + open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + + gui = SettingsDialog(common, testonion, qtapp, '/tmp/{}.json'.format(settings_filename), True) + return gui + + + @staticmethod + def tear_down(): + '''Clean up after tests''' + os.remove('/tmp/settings.json') diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py index e4e0a03f..c4c271b8 100644 --- a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py +++ b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py @@ -12,6 +12,10 @@ class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'Local404PublicModeRateLimitTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, True) diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py index ce26eae1..8f9caa7c 100644 --- a/tests/local_onionshare_404_triggers_ratelimit_test.py +++ b/tests/local_onionshare_404_triggers_ratelimit_test.py @@ -11,6 +11,10 @@ class Local404RateLimitTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'Local404RateLimitTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py index 6960301a..1f16b5fb 100644 --- a/tests/local_onionshare_receive_mode_sender_closed_test.py +++ b/tests/local_onionshare_receive_mode_sender_closed_test.py @@ -11,6 +11,10 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py index 1784ba94..039acff4 100644 --- a/tests/local_onionshare_receive_mode_timer_test.py +++ b/tests/local_onionshare_receive_mode_timer_test.py @@ -12,6 +12,10 @@ class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTimerTest') + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_timer_tests(False) diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index e33a6461..ee3a7215 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -11,6 +11,10 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_unwritable_dir_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index 2c7cbd5e..654895ca 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -12,6 +12,10 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(True, True) diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index fb5d36a7..c06d30cd 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -11,6 +11,10 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): } cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False, True) diff --git a/tests/local_onionshare_settings_dialog_test.py b/tests/local_onionshare_settings_dialog_test.py new file mode 100644 index 00000000..64840d7d --- /dev/null +++ b/tests/local_onionshare_settings_dialog_test.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +import json +import unittest +from PyQt5 import QtCore, QtTest + +from onionshare import strings +from .SettingsGuiBaseTest import SettingsGuiBaseTest + +class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): + @classmethod + def setUpClass(cls): + test_settings = { + "no_bridges": False, + "tor_bridges_use_obfs4": True, + } + cls.gui = SettingsGuiBaseTest.set_up(test_settings, 'settings') + + @classmethod + def tearDownClass(cls): + SettingsGuiBaseTest.tear_down() + + def test_gui(self): + self.gui.show() + # Window is shown + self.assertTrue(self.gui.isVisible()) + self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title')) + # Check for updates button is hidden + self.assertFalse(self.gui.check_for_updates_button.isVisible()) + + # public mode is off + self.assertFalse(self.gui.public_mode_checkbox.isChecked()) + # enable public mode + QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2)) + self.assertTrue(self.gui.public_mode_checkbox.isChecked()) + + # shutdown timer is off + self.assertFalse(self.gui.shutdown_timeout_checkbox.isChecked()) + # enable shutdown timer + QtTest.QTest.mouseClick(self.gui.shutdown_timeout_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.shutdown_timeout_checkbox.height()/2)) + self.assertTrue(self.gui.shutdown_timeout_checkbox.isChecked()) + + # legacy mode checkbox and related widgets + # legacy mode is off + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked()) + # persistence, stealth is hidden and disabled + self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertFalse(self.gui.save_private_key_checkbox.isChecked()) + self.assertFalse(self.gui.use_stealth_widget.isVisible()) + self.assertFalse(self.gui.stealth_checkbox.isChecked()) + self.assertFalse(self.gui.hidservauth_copy_button.isVisible()) + + # enable legacy mode + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked()) + self.assertTrue(self.gui.save_private_key_checkbox.isVisible()) + self.assertTrue(self.gui.use_stealth_widget.isVisible()) + # enable persistent mode + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + self.assertTrue(self.gui.save_private_key_checkbox.isChecked()) + # enable stealth mode + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + self.assertTrue(self.gui.stealth_checkbox.isChecked()) + # now that stealth, persistence are enabled, we can't turn off legacy mode + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) + # disable stealth, persistence + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + # legacy mode checkbox is enabled again + self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) + # uncheck legacy mode + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + # legacy options hidden again + self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertFalse(self.gui.use_stealth_widget.isVisible()) + # enable them all again so that we can see the setting stick in settings.json + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + + + # stay open toggled off, on + self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked()) + QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2)) + self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked()) + + # receive mode + self.gui.downloads_dir_lineedit.setText('/tmp/OnionShareSettingsTest') + # allow receiver shutdown is on + self.assertTrue(self.gui.receive_allow_receiver_shutdown_checkbox.isChecked()) + # disable receiver shutdown + QtTest.QTest.mouseClick(self.gui.receive_allow_receiver_shutdown_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.receive_allow_receiver_shutdown_checkbox.height()/2)) + self.assertFalse(self.gui.receive_allow_receiver_shutdown_checkbox.isChecked()) + + + # bundled mode is enabled + self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_bundled_radio.isChecked()) + # bridge options are shown + self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible()) + # bridges are set to obfs4 + self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked()) + self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked()) + + # custom bridges are hidden + self.assertFalse(self.gui.tor_bridges_use_custom_textbox_options.isVisible()) + # other modes are unchecked but enabled + self.assertTrue(self.gui.connection_type_automatic_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_control_port_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_socket_file_radio.isEnabled()) + self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) + self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) + self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked()) + + # enable automatic mode + QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2)) + self.assertTrue(self.gui.connection_type_automatic_radio.isChecked()) + # bundled is off + self.assertFalse(self.gui.connection_type_bundled_radio.isChecked()) + # bridges are hidden + self.assertFalse(self.gui.connection_type_bridges_radio_group.isVisible()) + + # auth type is hidden in bundled or automatic mode + self.assertFalse(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertFalse(self.gui.authenticate_password_radio.isVisible()) + + # enable control port mode + QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2)) + self.assertTrue(self.gui.connection_type_control_port_radio.isChecked()) + # automatic is off + self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) + # auth options appear + self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertTrue(self.gui.authenticate_password_radio.isVisible()) + + # enable socket mode + QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2)) + self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked()) + # control port is off + self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) + # auth options are still present + self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertTrue(self.gui.authenticate_password_radio.isVisible()) + + # re-enable bundled mode + QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2)) + # set some custom bridges + QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2)) + self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) + self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible()) + self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3') + + # Test that the Settings Dialog can save the settings and close itself + QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.isVisible()) + + # Test our settings are reflected in the settings json + with open('/tmp/settings.json') as f: + data = json.load(f) + + self.assertTrue(data["public_mode"]) + self.assertTrue(data["shutdown_timeout"]) + self.assertTrue(data["use_legacy_v2_onions"]) + self.assertTrue(data["save_private_key"]) + self.assertTrue(data["use_stealth"]) + self.assertEqual(data["downloads_dir"], "/tmp/OnionShareSettingsTest") + self.assertFalse(data["receive_allow_receiver_shutdown"]) + self.assertFalse(data["close_after_first_download"]) + self.assertEqual(data["connection_type"], "bundled") + self.assertFalse(data["tor_bridges_use_obfs4"]) + self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n") + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index 3b4ad0e8..f1c29990 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -11,6 +11,10 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, False) diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index dfe7f56c..af02e841 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -11,6 +11,10 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index 6b6b0bcc..53db0296 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -10,6 +10,10 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py index 6fe77752..b8017b77 100644 --- a/tests/local_onionshare_share_mode_large_download_test.py +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -10,6 +10,10 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_large_file_tests(False, True) diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py index a28f5a23..c273fbda 100644 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -14,6 +14,10 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_persistent_tests(False, True) diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index 9e3b0b5e..394dec21 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -12,6 +12,10 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): } cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_timer_tests(False) diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index 327a8456..dce84f62 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -13,6 +13,10 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py index 56b49c81..048186c3 100644 --- a/tests/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -13,6 +13,10 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): } cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + @classmethod + def tearDownClass(cls): + TorGuiReceiveTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py index 0fd8d5ed..d3965d59 100644 --- a/tests/onionshare_receive_mode_upload_test.py +++ b/tests/onionshare_receive_mode_upload_test.py @@ -12,6 +12,10 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): } cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + @classmethod + def tearDownClass(cls): + TorGuiReceiveTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index 9770cc35..83a009a1 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -11,6 +11,10 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py index 8409720a..8c5740c5 100644 --- a/tests/onionshare_share_mode_download_public_mode_test.py +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -12,6 +12,10 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py index c16f91e9..56ac924d 100644 --- a/tests/onionshare_share_mode_download_stay_open_test.py +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -12,6 +12,10 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py index 194328d9..125909d0 100644 --- a/tests/onionshare_share_mode_download_test.py +++ b/tests/onionshare_share_mode_download_test.py @@ -11,6 +11,10 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 3c283943..3f103a1a 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -16,6 +16,10 @@ class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index b414de1f..3096a8bc 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -13,6 +13,10 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py index b53905d0..9e690ec7 100644 --- a/tests/onionshare_share_mode_timer_test.py +++ b/tests/onionshare_share_mode_timer_test.py @@ -13,6 +13,10 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() -- cgit v1.2.3-54-g00ecf From dbbc9c0c820dd9fa211b7bf18b8d988923c15a6a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 09:23:07 +1100 Subject: Fix stealth test, add legacy v2 onion test --- tests/TorGuiBaseTest.py | 4 +--- tests/TorGuiShareTest.py | 7 ++++++- tests/onionshare_share_mode_stealth_test.py | 2 +- tests/onionshare_share_mode_v2_onion_test.py | 26 ++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/onionshare_share_mode_v2_onion_test.py diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index aee05096..611cb202 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -158,9 +158,7 @@ class TorGuiBaseTest(GuiBaseTest): def copy_have_hidserv_auth_button(self, mode): '''Test that the Copy HidservAuth button is shown''' self.assertTrue(mode.server_status.copy_hidservauth_button.isVisible()) - clipboard = self.gui.qtapp.clipboard() - self.assertRegex(clipboard.text(), r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) def hidserv_auth_string(self): '''Test the validity of the HidservAuth string''' - self.assertRegex(self.gui.app.auth_string, r'HidServAuth %s [a-zA-Z1-9]' % self.gui.app.onion_host) + self.assertRegex(self.gui.app.auth_string, r'HidServAuth {} [a-zA-Z1-9]'.format(self.gui.app.onion_host)) diff --git a/tests/TorGuiShareTest.py b/tests/TorGuiShareTest.py index ff8d0bb7..53641dce 100644 --- a/tests/TorGuiShareTest.py +++ b/tests/TorGuiShareTest.py @@ -36,11 +36,16 @@ class TorGuiShareTest(TorGuiBaseTest, GuiShareTest): '''Test that we have the same onion''' self.assertEqual(self.gui.app.onion_host, onion) + # legacy v2 onion test + def have_v2_onion(self): + '''Test that the onion is a v2 style onion''' + self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + self.assertEqual(len(self.gui.app.onion_host), 22) # 'Grouped' tests follow from here def run_all_share_mode_started_tests(self, public_mode): - """Tests in share mode after starting a share""" + '''Tests in share mode after starting a share''' self.server_working_on_start_button_pressed(self.gui.share_mode) self.server_status_indicator_says_starting(self.gui.share_mode) self.add_delete_buttons_hidden() diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index 3096a8bc..78e87e9e 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -22,8 +22,8 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() self.run_all_share_mode_started_tests(False) - self.copy_have_hidserv_auth_button(self.gui.share_mode) self.hidserv_auth_string() + self.copy_have_hidserv_auth_button(self.gui.share_mode) if __name__ == "__main__": unittest.main() diff --git a/tests/onionshare_share_mode_v2_onion_test.py b/tests/onionshare_share_mode_v2_onion_test.py new file mode 100644 index 00000000..f06f6631 --- /dev/null +++ b/tests/onionshare_share_mode_v2_onion_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "use_legacy_v2_onions": True, + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeV2OnionTest') + + @classmethod + def tearDownClass(cls): + TorGuiShareTest.tear_down() + + @pytest.mark.tor + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_tests(False, False) + self.have_v2_onion() + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From 386a8c5a2d54b21210a7f9b11d3beae768d62d3a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 10:47:55 +1100 Subject: Fix call to Alert() when an autostop timer has run out before starting the share --- onionshare_gui/server_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py index b86155f0..e34a3d16 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/server_status.py @@ -269,7 +269,7 @@ class ServerStatus(QtWidgets.QWidget): self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0) # If the timeout has actually passed already before the user hit Start, refuse to start the server. if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout: - Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning)) + Alert(self.common, strings._('gui_server_timeout_expired'), QtWidgets.QMessageBox.Warning) else: self.start_server() else: -- cgit v1.2.3-54-g00ecf From c79eedd626353cc68663fd3aa2bbab9dd753df86 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 11:57:21 +1100 Subject: Add better workaround for blocking QDialogs. Add unreadable file test and reinstate tor connection killed test --- tests/GuiBaseTest.py | 5 ++++ tests/GuiReceiveTest.py | 5 ++-- tests/GuiShareTest.py | 21 ++++++++++----- tests/TorGuiBaseTest.py | 9 +++++++ ...l_onionshare_share_mode_timer_too_short_test.py | 31 ++++++++++++++++++++++ ...l_onionshare_share_mode_unreadable_file_test.py | 22 +++++++++++++++ ...nshare_share_mode_tor_connection_killed_test.py | 25 +++++++++++++++++ 7 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 tests/local_onionshare_share_mode_timer_too_short_test.py create mode 100644 tests/local_onionshare_share_mode_unreadable_file_test.py create mode 100644 tests/onionshare_share_mode_tor_connection_killed_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index e2f194db..3b7ec4c4 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -303,6 +303,11 @@ class GuiBaseTest(object): # We should have timed out now self.assertEqual(mode.server_status.status, 0) + # Hack to close an Alert dialog that would otherwise block tests + def accept_dialog(self): + window = self.gui.qtapp.activeWindow() + if window: + window.close() # 'Grouped' tests follow from here diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 84d6a55a..0c0cb770 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -1,6 +1,6 @@ import os import requests -from PyQt5 import QtTest +from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest class GuiReceiveTest(GuiBaseTest): @@ -24,8 +24,7 @@ class GuiReceiveTest(GuiBaseTest): path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) - # A nasty hack to avoid the Alert dialog that blocks the rest of the test - self.gui.qtapp.exit() + QtCore.QTimer.singleShot(1000, self.accept_dialog) self.assertTrue('Error uploading, please inform the OnionShare user' in response.text) def upload_dir_permissions(self, mode=0o755): diff --git a/tests/GuiShareTest.py b/tests/GuiShareTest.py index 4f2f58e7..716bab73 100644 --- a/tests/GuiShareTest.py +++ b/tests/GuiShareTest.py @@ -12,10 +12,10 @@ class GuiShareTest(GuiBaseTest): self.assertEqual(self.gui.share_mode.server_status.web.slug, slug) # Share-specific tests - - def file_selection_widget_has_files(self): - '''Test that the number of items in the list is 2''' - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + def file_selection_widget_has_files(self, num=2): + '''Test that the number of items in the list is as expected''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), num) def deleting_all_files_hides_delete_button(self): @@ -40,14 +40,14 @@ class GuiShareTest(GuiBaseTest): '''Test that we can also delete a file by clicking on its [X] widget''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + self.file_selection_widget_has_files(0) def file_selection_widget_readd_files(self): '''Re-add some files to the list so we can share''' self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') - self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + self.file_selection_widget_has_files(2) def add_large_file(self): @@ -102,7 +102,7 @@ class GuiShareTest(GuiBaseTest): # A nasty hack to avoid the Alert dialog that blocks the rest of the test if not public_mode: - self.gui.qtapp.exit() + QtCore.QTimer.singleShot(1000, self.accept_dialog) # In public mode, we should still be running (no rate-limiting) if public_mode: @@ -195,3 +195,10 @@ class GuiShareTest(GuiBaseTest): self.server_timed_out(self.gui.share_mode, 10000) self.web_server_is_stopped() + + def run_all_share_mode_unreadable_file_tests(self): + '''Attempt to share an unreadable file''' + self.run_all_share_mode_setup_tests() + QtCore.QTimer.singleShot(1000, self.accept_dialog) + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/nonexistent.txt') + self.file_selection_widget_has_files(2) diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 611cb202..2c88bb94 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -162,3 +162,12 @@ class TorGuiBaseTest(GuiBaseTest): def hidserv_auth_string(self): '''Test the validity of the HidservAuth string''' self.assertRegex(self.gui.app.auth_string, r'HidServAuth {} [a-zA-Z1-9]'.format(self.gui.app.onion_host)) + + + + # Miscellaneous tests + def tor_killed_statusbar_message_shown(self, mode): + '''Test that the status bar message shows Tor was disconnected''' + self.gui.app.onion.c = None + QtTest.QTest.qWait(1000) + self.assertTrue(mode.status_bar.currentMessage(), strings._('gui_tor_connection_lost')) diff --git a/tests/local_onionshare_share_mode_timer_too_short_test.py b/tests/local_onionshare_share_mode_timer_too_short_test.py new file mode 100644 index 00000000..16153c3e --- /dev/null +++ b/tests/local_onionshare_share_mode_timer_too_short_test.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import unittest +from PyQt5 import QtCore, QtTest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": False, + "shutdown_timeout": True, + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTooShortTest') + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + # Set a low timeout + self.set_timeout(self.gui.share_mode, 2) + QtTest.QTest.qWait(3000) + QtCore.QTimer.singleShot(4000, self.accept_dialog) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEqual(self.gui.share_mode.server_status.status, 0) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_share_mode_unreadable_file_test.py b/tests/local_onionshare_share_mode_unreadable_file_test.py new file mode 100644 index 00000000..0e0970ea --- /dev/null +++ b/tests/local_onionshare_share_mode_unreadable_file_test.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiShareTest import GuiShareTest + +class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeUnReadableFileTest') + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_unreadable_file_tests() + +if __name__ == "__main__": + unittest.main() diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py new file mode 100644 index 00000000..382ed547 --- /dev/null +++ b/tests/onionshare_share_mode_tor_connection_killed_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +import pytest +import unittest + +from .TorGuiShareTest import TorGuiShareTest + +class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') + + @pytest.mark.tor + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + self.run_all_share_mode_started_tests(False) + self.tor_killed_statusbar_message_shown(self.gui.share_mode) + self.server_is_stopped(self.gui.share_mode, False) + self.web_server_is_stopped() + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From f7ab3050492d0995b543ac42d9eb011b69551f8b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 13:48:13 +1100 Subject: Add simple test to ensure we can click the settings button --- .../local_onionshare_open_settings_dialog_test.py | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/local_onionshare_open_settings_dialog_test.py diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py new file mode 100644 index 00000000..2a4d1a01 --- /dev/null +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import pytest +import unittest +from PyQt5 import QtCore, QtTest + +from .GuiShareTest import GuiShareTest + +class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalOpenSettingsDialogTest') + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + # Make sure we can open the settings dialog via the settings button + QtCore.QTimer.singleShot(1000, self.accept_dialog) + QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From 03879ce9879220be56068730e5f63d05db715b0d Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 14:33:31 +1100 Subject: Add a test for making sure quitting during a share prompts before shutting down share --- .../local_onionshare_open_settings_dialog_test.py | 1 - ...e_quitting_during_share_prompts_warning_test.py | 31 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/local_onionshare_quitting_during_share_prompts_warning_test.py diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py index 2a4d1a01..b012b6a2 100644 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import unittest from PyQt5 import QtCore, QtTest diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py new file mode 100644 index 00000000..f6425d1b --- /dev/null +++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import unittest +from PyQt5 import QtCore, QtTest + +from .GuiShareTest import GuiShareTest + +class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + "close_after_first_download": False + } + cls.gui = GuiShareTest.set_up(test_settings, 'LocalQuittingDuringSharePromptsWarningTest') + + #@classmethod + #def tearDownClass(cls): + # TorGuiShareTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_tests(False, True) + # Prepare our auto-accept of prompt + QtCore.QTimer.singleShot(5000, self.accept_dialog) + # Try to close the app + self.gui.close() + # Server should still be running (we've been prompted first) + self.server_is_started(self.gui.share_mode, 0) + self.web_server_is_running() + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From b826528603fbed39a87c56ebef7c37430468447e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 14:34:29 +1100 Subject: Remove commented out teardownClass (even though the teardown isn't working atm :/) --- .../local_onionshare_quitting_during_share_prompts_warning_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py index f6425d1b..b33b991c 100644 --- a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py +++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py @@ -12,9 +12,9 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest } cls.gui = GuiShareTest.set_up(test_settings, 'LocalQuittingDuringSharePromptsWarningTest') - #@classmethod - #def tearDownClass(cls): - # TorGuiShareTest.tear_down() + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() def test_gui(self): self.run_all_common_setup_tests() -- cgit v1.2.3-54-g00ecf From a093d41102952a16c0f43ba1ba9ea4ead5d0db0c Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 15:21:04 +1100 Subject: More test coverage, particularly of Receive Mode --- tests/GuiBaseTest.py | 2 +- tests/GuiReceiveTest.py | 20 ++++++++++++++---- tests/TorGuiReceiveTest.py | 12 +++++++---- ...ode_public_mode_upload_non_writable_dir_test.py | 24 ++++++++++++++++++++++ ...l_onionshare_receive_mode_sender_closed_test.py | 4 ++-- ...re_receive_mode_upload_non_writable_dir_test.py | 4 ++-- tests/local_onionshare_settings_dialog_test.py | 11 +++++++--- ...al_onionshare_share_mode_large_download_test.py | 4 ++-- 8 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 3b7ec4c4..0e570412 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -29,7 +29,7 @@ class GuiBaseTest(object): # Create a test dir and files if not os.path.exists('/tmp/testdir'): testdir = os.mkdir('/tmp/testdir') - testfile = open('/tmp/testdir/test.txt', 'w') + testfile = open('/tmp/testdir/test', 'w') testfile.write('onionshare') testfile.close() diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index 0c0cb770..a659a79f 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -4,9 +4,9 @@ from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, expected_file): + def upload_file(self, public_mode, file_to_upload, expected_file): '''Test that we can upload the file''' - files = {'file[]': open('/tmp/test.txt', 'rb')} + files = {'file[]': open(file_to_upload, 'rb')} if not public_mode: path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) else: @@ -31,6 +31,12 @@ class GuiReceiveTest(GuiBaseTest): '''Manipulate the permissions on the upload dir in between tests''' os.chmod('/tmp/OnionShare', mode) + def try_public_paths_in_non_public_mode(self): + response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port)) + self.assertEqual(response.status_code, 404) + response = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) + self.assertEqual(response.status_code, 404) + def run_receive_mode_sender_closed_tests(self, public_mode): '''Test that the share can be stopped by the sender in receive mode''' if not public_mode: @@ -65,11 +71,17 @@ class GuiReceiveTest(GuiBaseTest): def run_all_receive_mode_tests(self, public_mode, receive_allow_receiver_shutdown): '''Upload files in receive mode and stop the share''' self.run_all_receive_mode_setup_tests(public_mode) - self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + if not public_mode: + self.try_public_paths_in_non_public_mode() + self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt') self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt') self.counter_incremented(self.gui.receive_mode, 2) + self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test') + self.counter_incremented(self.gui.receive_mode, 3) + self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2') + self.counter_incremented(self.gui.receive_mode, 4) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) self.web_server_is_stopped() diff --git a/tests/TorGuiReceiveTest.py b/tests/TorGuiReceiveTest.py index 3c380b8a..a21dd4fc 100644 --- a/tests/TorGuiReceiveTest.py +++ b/tests/TorGuiReceiveTest.py @@ -5,13 +5,13 @@ from .TorGuiBaseTest import TorGuiBaseTest class TorGuiReceiveTest(TorGuiBaseTest): - def upload_file(self, public_mode, expected_file): + def upload_file(self, public_mode, file_to_upload, expected_file): '''Test that we can upload the file''' (socks_address, socks_port) = self.gui.app.onion.get_tor_socks_port() session = requests.session() session.proxies = {} session.proxies['http'] = 'socks5h://{}:{}'.format(socks_address, socks_port) - files = {'file[]': open('/tmp/test.txt', 'rb')} + files = {'file[]': open(file_to_upload, 'rb')} if not public_mode: path = 'http://{}/{}/upload'.format(self.gui.app.onion_host, self.gui.receive_mode.web.slug) else: @@ -40,11 +40,15 @@ class TorGuiReceiveTest(TorGuiBaseTest): self.have_copy_url_button(self.gui.receive_mode, public_mode) self.server_status_indicator_says_started(self.gui.receive_mode) self.web_page(self.gui.receive_mode, 'Select the files you want to send, then click', public_mode) - self.upload_file(public_mode, '/tmp/OnionShare/test.txt') + self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt') self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/OnionShare/test-2.txt') + self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt') self.counter_incremented(self.gui.receive_mode, 2) + self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test') + self.counter_incremented(self.gui.receive_mode, 3) + self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2') + self.counter_incremented(self.gui.receive_mode, 4) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) self.web_server_is_stopped() diff --git a/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py new file mode 100644 index 00000000..2bffb4dd --- /dev/null +++ b/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceivePublicModeUnwritableTest') + + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_receive_mode_unwritable_dir_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py index 1f16b5fb..3a0d8617 100644 --- a/tests/local_onionshare_receive_mode_sender_closed_test.py +++ b/tests/local_onionshare_receive_mode_sender_closed_test.py @@ -3,13 +3,13 @@ import unittest from .GuiReceiveTest import GuiReceiveTest -class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): +class LocalReceiveModeSenderClosedTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeSenderClosedTest') @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index ee3a7215..b6f06b08 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -3,13 +3,13 @@ import unittest from .GuiReceiveTest import GuiReceiveTest -class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): +class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): @classmethod def setUpClass(cls): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeUnwritableTest') @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_settings_dialog_test.py b/tests/local_onionshare_settings_dialog_test.py index 64840d7d..a328f53b 100644 --- a/tests/local_onionshare_settings_dialog_test.py +++ b/tests/local_onionshare_settings_dialog_test.py @@ -11,7 +11,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): def setUpClass(cls): test_settings = { "no_bridges": False, - "tor_bridges_use_obfs4": True, + "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", } cls.gui = SettingsGuiBaseTest.set_up(test_settings, 'settings') @@ -97,8 +97,12 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): self.assertTrue(self.gui.connection_type_bundled_radio.isChecked()) # bridge options are shown self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible()) - # bridges are set to obfs4 + # bridges are set to custom self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked()) + self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) + + # switch to obfs4 + QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2)) self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked()) # custom bridges are hidden @@ -143,10 +147,11 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): # re-enable bundled mode QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2)) - # set some custom bridges + # go back to custom bridges QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2)) self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible()) + self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked()) self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3') # Test that the Settings Dialog can save the settings and close itself diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py index b8017b77..5f5de888 100644 --- a/tests/local_onionshare_share_mode_large_download_test.py +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -3,12 +3,12 @@ import unittest from .GuiShareTest import GuiShareTest -class LocalShareModeTest(unittest.TestCase, GuiShareTest): +class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest): @classmethod def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeLargeDownloadTest') @classmethod def tearDownClass(cls): -- cgit v1.2.3-54-g00ecf From b749fc8d12c18aba91adcaf62154a63d7ebd6af3 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:31:51 +1100 Subject: Remove unique settings file per test, because they don't run concurrently anymore --- tests/GuiBaseTest.py | 7 ++++--- tests/SettingsGuiBaseTest.py | 6 +++--- tests/TorGuiBaseTest.py | 6 +++--- ...onshare_404_public_mode_skips_ratelimit_test.py | 2 +- ...local_onionshare_404_triggers_ratelimit_test.py | 2 +- .../local_onionshare_open_settings_dialog_test.py | 2 +- ...e_quitting_during_share_prompts_warning_test.py | 2 +- ...ode_public_mode_upload_non_writable_dir_test.py | 24 ---------------------- ...l_onionshare_receive_mode_sender_closed_test.py | 2 +- tests/local_onionshare_receive_mode_timer_test.py | 2 +- ...re_receive_mode_upload_non_writable_dir_test.py | 2 +- ...ode_upload_public_mode_non_writable_dir_test.py | 24 ++++++++++++++++++++++ ...onshare_receive_mode_upload_public_mode_test.py | 2 +- tests/local_onionshare_receive_mode_upload_test.py | 2 +- tests/local_onionshare_settings_dialog_test.py | 2 +- ...onshare_share_mode_download_public_mode_test.py | 2 +- ...nionshare_share_mode_download_stay_open_test.py | 2 +- tests/local_onionshare_share_mode_download_test.py | 2 +- ...al_onionshare_share_mode_large_download_test.py | 2 +- ...l_onionshare_share_mode_slug_persistent_test.py | 2 +- tests/local_onionshare_share_mode_timer_test.py | 2 +- ...l_onionshare_share_mode_timer_too_short_test.py | 2 +- ...l_onionshare_share_mode_unreadable_file_test.py | 2 +- .../onionshare_790_cancel_on_second_share_test.py | 2 +- ...onshare_receive_mode_upload_public_mode_test.py | 2 +- tests/onionshare_receive_mode_upload_test.py | 2 +- tests/onionshare_share_mode_cancel_share_test.py | 2 +- ...onshare_share_mode_download_public_mode_test.py | 2 +- ...nionshare_share_mode_download_stay_open_test.py | 2 +- tests/onionshare_share_mode_download_test.py | 2 +- tests/onionshare_share_mode_persistent_test.py | 2 +- tests/onionshare_share_mode_stealth_test.py | 2 +- tests/onionshare_share_mode_timer_test.py | 2 +- ...nshare_share_mode_tor_connection_killed_test.py | 2 +- tests/onionshare_share_mode_v2_onion_test.py | 2 +- 35 files changed, 64 insertions(+), 63 deletions(-) delete mode 100644 tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py create mode 100644 tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 0e570412..af5f1944 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -19,7 +19,7 @@ from onionshare_gui.mode.receive_mode import ReceiveMode class GuiBaseTest(object): @staticmethod - def set_up(test_settings, settings_filename): + def set_up(test_settings): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -51,9 +51,9 @@ class GuiBaseTest(object): app = OnionShare(common, testonion, True, 0) web = Web(common, False, True) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/{}.json'.format(settings_filename), True) + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', True) return gui @staticmethod @@ -61,6 +61,7 @@ class GuiBaseTest(object): '''Clean up after tests''' try: os.remove('/tmp/test.txt') + os.remove('/tmp/settings.json') os.remove('/tmp/largefile') shutil.rmtree('/tmp/OnionShare') shutil.rmtree('/tmp/testdir') diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index dac074fc..195e7933 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -13,7 +13,7 @@ from onionshare_gui.settings_dialog import SettingsDialog class SettingsGuiBaseTest(object): @staticmethod - def set_up(test_settings, settings_filename): + def set_up(test_settings): '''Create the GUI''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -37,9 +37,9 @@ class SettingsGuiBaseTest(object): if key not in test_settings: test_settings[key] = val - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) - gui = SettingsDialog(common, testonion, qtapp, '/tmp/{}.json'.format(settings_filename), True) + gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json', True) return gui diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 2c88bb94..9a0bda3e 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -18,7 +18,7 @@ from .GuiBaseTest import GuiBaseTest class TorGuiBaseTest(GuiBaseTest): @staticmethod - def set_up(test_settings, settings_filename): + def set_up(test_settings): '''Create GUI with given settings''' # Create our test file testfile = open('/tmp/test.txt', 'w') @@ -51,9 +51,9 @@ class TorGuiBaseTest(GuiBaseTest): app = OnionShare(common, testonion, False, 0) web = Web(common, False, False) - open('/tmp/{}.json'.format(settings_filename), 'w').write(json.dumps(test_settings)) + open('/tmp/settings.json', 'w').write(json.dumps(test_settings)) - gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/{}.json'.format(settings_filename), False) + gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt', '/tmp/testdir'], '/tmp/settings.json', False) return gui def history_indicator(self, mode, public_mode): diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py index c4c271b8..11feb6f0 100644 --- a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py +++ b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py @@ -10,7 +10,7 @@ class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): "close_after_first_download": False, "public_mode": True } - cls.gui = GuiShareTest.set_up(test_settings, 'Local404PublicModeRateLimitTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py index 8f9caa7c..ad49c3f8 100644 --- a/tests/local_onionshare_404_triggers_ratelimit_test.py +++ b/tests/local_onionshare_404_triggers_ratelimit_test.py @@ -9,7 +9,7 @@ class Local404RateLimitTest(unittest.TestCase, GuiShareTest): test_settings = { "close_after_first_download": False } - cls.gui = GuiShareTest.set_up(test_settings, 'Local404RateLimitTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py index b012b6a2..61e66be2 100644 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -9,7 +9,7 @@ class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalOpenSettingsDialogTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py index b33b991c..d2fe4986 100644 --- a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py +++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py @@ -10,7 +10,7 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest test_settings = { "close_after_first_download": False } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalQuittingDuringSharePromptsWarningTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py deleted file mode 100644 index 2bffb4dd..00000000 --- a/tests/local_onionshare_receive_mode_public_mode_upload_non_writable_dir_test.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import unittest - -from .GuiReceiveTest import GuiReceiveTest - -class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): - @classmethod - def setUpClass(cls): - test_settings = { - "public_mode": True, - "receive_allow_receiver_shutdown": True - } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceivePublicModeUnwritableTest') - - @classmethod - def tearDownClass(cls): - GuiReceiveTest.tear_down() - - def test_gui(self): - self.run_all_common_setup_tests() - self.run_all_receive_mode_unwritable_dir_tests(True, True) - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py index 3a0d8617..e177d2ef 100644 --- a/tests/local_onionshare_receive_mode_sender_closed_test.py +++ b/tests/local_onionshare_receive_mode_sender_closed_test.py @@ -9,7 +9,7 @@ class LocalReceiveModeSenderClosedTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeSenderClosedTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py index 039acff4..88002f94 100644 --- a/tests/local_onionshare_receive_mode_timer_test.py +++ b/tests/local_onionshare_receive_mode_timer_test.py @@ -10,7 +10,7 @@ class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTimerTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index b6f06b08..7d7b2780 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -9,7 +9,7 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeUnwritableTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py new file mode 100644 index 00000000..cdc4e62a --- /dev/null +++ b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import unittest + +from .GuiReceiveTest import GuiReceiveTest + +class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): + @classmethod + def setUpClass(cls): + test_settings = { + "public_mode": True, + "receive_allow_receiver_shutdown": True + } + cls.gui = GuiReceiveTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiReceiveTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_receive_mode_unwritable_dir_tests(True, True) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index 654895ca..bedb7ae2 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -10,7 +10,7 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModePublicModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index c06d30cd..82baf3fd 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -9,7 +9,7 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = GuiReceiveTest.set_up(test_settings, 'LocalReceiveModeTest') + cls.gui = GuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_settings_dialog_test.py b/tests/local_onionshare_settings_dialog_test.py index a328f53b..6d8923b6 100644 --- a/tests/local_onionshare_settings_dialog_test.py +++ b/tests/local_onionshare_settings_dialog_test.py @@ -13,7 +13,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): "no_bridges": False, "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", } - cls.gui = SettingsGuiBaseTest.set_up(test_settings, 'settings') + cls.gui = SettingsGuiBaseTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index f1c29990..d6dff13a 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -9,7 +9,7 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): test_settings = { "public_mode": True, } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePublicModeTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index af02e841..54d6de51 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -9,7 +9,7 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): test_settings = { "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeStayOpenTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index 53db0296..ff182740 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -8,7 +8,7 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py index 5f5de888..46e6df28 100644 --- a/tests/local_onionshare_share_mode_large_download_test.py +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -8,7 +8,7 @@ class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeLargeDownloadTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py index c273fbda..a1cc6972 100644 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -12,7 +12,7 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): "save_private_key": True, "close_after_first_download": False, } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModePersistentSlugTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index 394dec21..41a6268d 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -10,7 +10,7 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_timer_too_short_test.py b/tests/local_onionshare_share_mode_timer_too_short_test.py index 16153c3e..41c30883 100644 --- a/tests/local_onionshare_share_mode_timer_too_short_test.py +++ b/tests/local_onionshare_share_mode_timer_too_short_test.py @@ -11,7 +11,7 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeTimerTooShortTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/local_onionshare_share_mode_unreadable_file_test.py b/tests/local_onionshare_share_mode_unreadable_file_test.py index 0e0970ea..38a0e847 100644 --- a/tests/local_onionshare_share_mode_unreadable_file_test.py +++ b/tests/local_onionshare_share_mode_unreadable_file_test.py @@ -8,7 +8,7 @@ class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = GuiShareTest.set_up(test_settings, 'LocalShareModeUnReadableFileTest') + cls.gui = GuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index dce84f62..b144edf3 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -11,7 +11,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): test_settings = { "close_after_first_download": True } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelSecondShareTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py index 048186c3..275e5953 100644 --- a/tests/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -11,7 +11,7 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): "public_mode": True, "receive_allow_receiver_shutdown": True } - cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + cls.gui = TorGuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py index d3965d59..f9914659 100644 --- a/tests/onionshare_receive_mode_upload_test.py +++ b/tests/onionshare_receive_mode_upload_test.py @@ -10,7 +10,7 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): test_settings = { "receive_allow_receiver_shutdown": True } - cls.gui = TorGuiReceiveTest.set_up(test_settings, 'ReceiveModeTest') + cls.gui = TorGuiReceiveTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index 83a009a1..5f4d6fb3 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -9,7 +9,7 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeCancelTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py index 8c5740c5..672603ce 100644 --- a/tests/onionshare_share_mode_download_public_mode_test.py +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -10,7 +10,7 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): test_settings = { "public_mode": True, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePublicModeTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py index 56ac924d..e7e64083 100644 --- a/tests/onionshare_share_mode_download_stay_open_test.py +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -10,7 +10,7 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): test_settings = { "close_after_first_download": False, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStayOpenTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py index 125909d0..7d414e5d 100644 --- a/tests/onionshare_share_mode_download_test.py +++ b/tests/onionshare_share_mode_download_test.py @@ -9,7 +9,7 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 3f103a1a..86b61a81 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -14,7 +14,7 @@ class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): "save_private_key": True, "close_after_first_download": False, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModePersistentSlugTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index 78e87e9e..b16669e6 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -11,7 +11,7 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): "use_legacy_v2_onions": True, "use_stealth": True, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeStealthTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py index 9e690ec7..a13d2d80 100644 --- a/tests/onionshare_share_mode_timer_test.py +++ b/tests/onionshare_share_mode_timer_test.py @@ -11,7 +11,7 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): "public_mode": False, "shutdown_timeout": True, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTimerTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py index 382ed547..62513a12 100644 --- a/tests/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests/onionshare_share_mode_tor_connection_killed_test.py @@ -9,7 +9,7 @@ class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): def setUpClass(cls): test_settings = { } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeTorConnectionKilledTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @pytest.mark.tor def test_gui(self): diff --git a/tests/onionshare_share_mode_v2_onion_test.py b/tests/onionshare_share_mode_v2_onion_test.py index f06f6631..c932abf9 100644 --- a/tests/onionshare_share_mode_v2_onion_test.py +++ b/tests/onionshare_share_mode_v2_onion_test.py @@ -10,7 +10,7 @@ class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest): test_settings = { "use_legacy_v2_onions": True, } - cls.gui = TorGuiShareTest.set_up(test_settings, 'ShareModeV2OnionTest') + cls.gui = TorGuiShareTest.set_up(test_settings) @classmethod def tearDownClass(cls): -- cgit v1.2.3-54-g00ecf From 22e566784238e5da76a3a311a097a00dc482457b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:36:58 +1100 Subject: raise timer seuqnce on open settings dialog test (in case that's why it's segfaulting in Travis) --- tests/local_onionshare_open_settings_dialog_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py index 61e66be2..af20c73f 100644 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -19,7 +19,7 @@ class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() # Make sure we can open the settings dialog via the settings button - QtCore.QTimer.singleShot(1000, self.accept_dialog) + QtCore.QTimer.singleShot(4000, self.accept_dialog) QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) if __name__ == "__main__": -- cgit v1.2.3-54-g00ecf From 497cd4fbdd06ea4b3c4ead289933aa83a168e0c7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:45:52 +1100 Subject: Revert "raise timer seuqnce on open settings dialog test (in case that's why it's segfaulting in Travis)" This reverts commit 22e566784238e5da76a3a311a097a00dc482457b. --- tests/local_onionshare_open_settings_dialog_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py index af20c73f..61e66be2 100644 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -19,7 +19,7 @@ class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() # Make sure we can open the settings dialog via the settings button - QtCore.QTimer.singleShot(4000, self.accept_dialog) + QtCore.QTimer.singleShot(1000, self.accept_dialog) QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) if __name__ == "__main__": -- cgit v1.2.3-54-g00ecf From 7820c13a421b9aa515733c99f0258f9b57b3fba7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:51:49 +1100 Subject: Add initial .circleci config --- .circleci/config.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..17e8979c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,43 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` + - image: circleci/python:3.6.1 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "install/requirements-tests.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install -r install/requirements-tests.txt + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "install/requirements-tests.txt" }} + + # run tests! + - run: + name: run tests + command: | + . venv/bin/activate + xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ + -- cgit v1.2.3-54-g00ecf From ce5d978a8feba7069173b9fbefbb0f7ecbf14ec5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:55:15 +1100 Subject: try to get circleci to build just this branch for now --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 17e8979c..79f902c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,9 @@ version: 2 jobs: build: + branches: + only: + - fix_tor_tests docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` -- cgit v1.2.3-54-g00ecf From d40351e0af4b7da08d246615ab15571495780f31 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:58:05 +1100 Subject: circleci tweaks --- .circleci/config.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 79f902c5..6adcdfa9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,27 +18,26 @@ jobs: steps: - checkout - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "install/requirements-tests.txt" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: name: install dependencies command: | + sudo apt-get update && sudo apt-get install python3-pyqt5 python3 -m venv venv . venv/bin/activate + pip install -r install/requirements.txt + pip install -r install/requirements-tests.txt + pip install pytest-cov flake8 pip install -r install/requirements-tests.txt - - save_cache: - paths: - - ./venv - key: v1-dependencies-{{ checksum "install/requirements-tests.txt" }} - # run tests! - run: + name: run flask tests + command: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + name: run tests command: | . venv/bin/activate -- cgit v1.2.3-54-g00ecf From 67d75826aeb55ce1277324daf8cb6e0d317149f2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 16:59:13 +1100 Subject: circleci tweaks --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6adcdfa9..f50a98f7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,11 +33,13 @@ jobs: - run: name: run flask tests command: | + . venv/bin/activate # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - run: name: run tests command: | . venv/bin/activate -- cgit v1.2.3-54-g00ecf From 68bff3fd50240e174aa311c44d1112a1424ed7af Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:04:24 +1100 Subject: circleci tweaks --- .circleci/config.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f50a98f7..ff902d04 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,18 +22,14 @@ jobs: name: install dependencies command: | sudo apt-get update && sudo apt-get install python3-pyqt5 - python3 -m venv venv - . venv/bin/activate - pip install -r install/requirements.txt - pip install -r install/requirements-tests.txt - pip install pytest-cov flake8 - pip install -r install/requirements-tests.txt + pip3 install -r install/requirements.txt + pip3 install -r install/requirements-tests.txt + pip3 install pytest-cov flake8 # run tests! - run: name: run flask tests command: | - . venv/bin/activate # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide @@ -42,6 +38,5 @@ jobs: - run: name: run tests command: | - . venv/bin/activate xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ -- cgit v1.2.3-54-g00ecf From 0d48d2a3548beb2d951a0f32c23132dbff5f6e16 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:15:13 +1100 Subject: sudo --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ff902d04..ba6d4ab8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,9 +22,9 @@ jobs: name: install dependencies command: | sudo apt-get update && sudo apt-get install python3-pyqt5 - pip3 install -r install/requirements.txt - pip3 install -r install/requirements-tests.txt - pip3 install pytest-cov flake8 + sudo pip3 install -r install/requirements.txt + sudo pip3 install -r install/requirements-tests.txt + sudo pip3 install pytest-cov flake8 # run tests! - run: -- cgit v1.2.3-54-g00ecf From bc85d1a9af9a21eeed37f0cb284920c80ad9ed47 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:15:34 +1100 Subject: remove branch specific config --- .circleci/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ba6d4ab8..52576430 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,9 +5,6 @@ version: 2 jobs: build: - branches: - only: - - fix_tor_tests docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` -- cgit v1.2.3-54-g00ecf From a68a0dbabfe9a108014bed0e3fe6ae05c22b637a Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:17:43 +1100 Subject: Travis and CircleCI hate this simple test --- .../local_onionshare_open_settings_dialog_test.py | 26 ---------------------- 1 file changed, 26 deletions(-) delete mode 100644 tests/local_onionshare_open_settings_dialog_test.py diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py deleted file mode 100644 index 61e66be2..00000000 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -import unittest -from PyQt5 import QtCore, QtTest - -from .GuiShareTest import GuiShareTest - -class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() - - def test_gui(self): - self.run_all_common_setup_tests() - self.run_all_share_mode_setup_tests() - # Make sure we can open the settings dialog via the settings button - QtCore.QTimer.singleShot(1000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From bbff74986221336af0fc5114708643fabc230d78 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:23:25 +1100 Subject: Fix path to large_file in teardown class --- tests/GuiBaseTest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index af5f1944..68f18f72 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -62,7 +62,7 @@ class GuiBaseTest(object): try: os.remove('/tmp/test.txt') os.remove('/tmp/settings.json') - os.remove('/tmp/largefile') + os.remove('/tmp/large_file') shutil.rmtree('/tmp/OnionShare') shutil.rmtree('/tmp/testdir') except: -- cgit v1.2.3-54-g00ecf From 3538e9d19e8d93198529b409154a947c97e01685 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:28:13 +1100 Subject: run on same version of python as me --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 52576430..23bf4806 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: circleci/python:3.6.1 + - image: circleci/python:3.6.6 working_directory: ~/repo -- cgit v1.2.3-54-g00ecf From 59667b2d1d798449f1d23e94e6ee6a59a8bf6bbb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:29:44 +1100 Subject: more cleanup in teardown class --- tests/GuiBaseTest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index 68f18f72..c557fc15 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -63,8 +63,10 @@ class GuiBaseTest(object): os.remove('/tmp/test.txt') os.remove('/tmp/settings.json') os.remove('/tmp/large_file') - shutil.rmtree('/tmp/OnionShare') + os.remove('/tmp/download.zip') + os.remove('/tmp/webpage') shutil.rmtree('/tmp/testdir') + shutil.rmtree('/tmp/OnionShare') except: pass -- cgit v1.2.3-54-g00ecf From cddfd5b0a47772ed3b2b24b061b409710bcb2101 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:38:28 +1100 Subject: see if it's a version issue --- install/requirements-tests.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index b931afd1..849e1166 100644 --- a/install/requirements-tests.txt +++ b/install/requirements-tests.txt @@ -1,9 +1,9 @@ atomicwrites==1.2.1 attrs==18.2.0 more-itertools==4.3.0 -pluggy==0.6.0 -py==1.6.0 -pytest==3.4.2 +pluggy==0.7.1 +py==1.7.0 +pytest==3.8.2 pytest-faulthandler==1.5.0 pytest-qt==3.1.0 six==1.11.0 -- cgit v1.2.3-54-g00ecf From f608b79aafde24c0a7cf5fc8ca4c2320be3f2e72 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:50:16 +1100 Subject: The only other version difference I can find is PyQt itself --- .circleci/config.yml | 1 - install/requirements.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23bf4806..3594e9f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,6 @@ jobs: - run: name: install dependencies command: | - sudo apt-get update && sudo apt-get install python3-pyqt5 sudo pip3 install -r install/requirements.txt sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 diff --git a/install/requirements.txt b/install/requirements.txt index 32ec6887..f09d2e95 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -17,7 +17,7 @@ pycparser==2.18 pycryptodome==3.6.6 PyInstaller==3.4 PyNaCl==1.2.1 -PyQt5==5.11.2 +PyQt5==5.11.3 PyQt5-sip==4.19.12 PySocks==1.6.8 requests==2.19.1 -- cgit v1.2.3-54-g00ecf From cfbfc4a66603e7dff686922dee16c24da6d4a23e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:51:45 +1100 Subject: Revert "The only other version difference I can find is PyQt itself" This reverts commit f608b79aafde24c0a7cf5fc8ca4c2320be3f2e72. --- .circleci/config.yml | 1 + install/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3594e9f8..23bf4806 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,6 +18,7 @@ jobs: - run: name: install dependencies command: | + sudo apt-get update && sudo apt-get install python3-pyqt5 sudo pip3 install -r install/requirements.txt sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 diff --git a/install/requirements.txt b/install/requirements.txt index f09d2e95..32ec6887 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -17,7 +17,7 @@ pycparser==2.18 pycryptodome==3.6.6 PyInstaller==3.4 PyNaCl==1.2.1 -PyQt5==5.11.3 +PyQt5==5.11.2 PyQt5-sip==4.19.12 PySocks==1.6.8 requests==2.19.1 -- cgit v1.2.3-54-g00ecf From c47f974dad3cfdf37cbd0d791c597bac1d3f4224 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 17:59:16 +1100 Subject: Tweaks to SettingsGuiBaseTest object --- tests/SettingsGuiBaseTest.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 195e7933..e59a58a8 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -1,13 +1,10 @@ import json import os -import sys -from PyQt5 import QtWidgets from onionshare import strings 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 from onionshare_gui.settings_dialog import SettingsDialog @@ -31,8 +28,6 @@ class SettingsGuiBaseTest(object): qtapp = Application(common) app = OnionShare(common, testonion, True, 0) - web = Web(common, False, True) - for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val -- cgit v1.2.3-54-g00ecf From f1bf8966a00ba6b62eb9b0c22205a38dbf13399d Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 17 Oct 2018 18:04:38 +1100 Subject: pytest-qt is 3.2.1 on my machine --- install/requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index 849e1166..57e98ce1 100644 --- a/install/requirements-tests.txt +++ b/install/requirements-tests.txt @@ -5,6 +5,6 @@ pluggy==0.7.1 py==1.7.0 pytest==3.8.2 pytest-faulthandler==1.5.0 -pytest-qt==3.1.0 +pytest-qt==3.2.1 six==1.11.0 urllib3==1.23 -- cgit v1.2.3-54-g00ecf From ced8948eb88457e1a106cc179b8ed8b1cc4109b2 Mon Sep 17 00:00:00 2001 From: Austin Jackson Date: Wed, 17 Oct 2018 13:19:35 -0500 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55487045..fbb9c914 100644 --- a/README.md +++ b/README.md @@ -18,5 +18,5 @@ You can set up your development environment to build OnionShare yourself by foll # Screenshots -![Server Screenshot](/screenshots/server.png) +![Server Screenshot](/screenshots/appdata-server.png) ![Client Screenshot](/screenshots/client.png) -- cgit v1.2.3-54-g00ecf From 72e684c166422cbe44445b684aa9961aa3ee77e8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 16:57:05 +1100 Subject: try and build off buster --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23bf4806..4ad43577 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: circleci/python:3.6.6 + - image: debian/buster working_directory: ~/repo @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - sudo apt-get update && sudo apt-get install python3-pyqt5 + sudo apt-get update && sudo apt-get install python3-pyqt5 python3-pip sudo pip3 install -r install/requirements.txt sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From d5881286be301ad631418a296227df60dc6e4bbc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 16:57:59 +1100 Subject: buster is a tag --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ad43577..2585995a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: debian/buster + - image: debian:buster working_directory: ~/repo -- cgit v1.2.3-54-g00ecf From eb63a045f3ac7ca11b3321965b892dda2fdc5a4b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 16:59:49 +1100 Subject: see if we can avoid sudo --- .circleci/config.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2585995a..14a4a8c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,10 +18,10 @@ jobs: - run: name: install dependencies command: | - sudo apt-get update && sudo apt-get install python3-pyqt5 python3-pip - sudo pip3 install -r install/requirements.txt - sudo pip3 install -r install/requirements-tests.txt - sudo pip3 install pytest-cov flake8 + apt-get update && apt-get install python3-pyqt5 python3-pip + pip3 install -r install/requirements.txt + pip3 install -r install/requirements-tests.txt + pip3 install pytest-cov flake8 # run tests! - run: -- cgit v1.2.3-54-g00ecf From 5972665fc35051670cebebed8ffd263a334c3800 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:00:46 +1100 Subject: avoid prompt --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 14a4a8c7..e6974459 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install python3-pyqt5 python3-pip + apt-get update && apt-get install -y python3-pyqt5 python3-pip pip3 install -r install/requirements.txt pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From 0c328b8c55014e5c14697ef065def2fd941e9598 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:01:33 +1100 Subject: xvfb should be installed --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e6974459..5c05cf13 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install -y python3-pyqt5 python3-pip + apt-get update && apt-get install -y python3-pyqt5 python3-pip xvfb pip3 install -r install/requirements.txt pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From 638651ceec4d6e8fa2b8efae35e6f28614a4198e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:11:39 +1100 Subject: don't install main requirements.txt --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c05cf13..1a985254 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,6 @@ jobs: name: install dependencies command: | apt-get update && apt-get install -y python3-pyqt5 python3-pip xvfb - pip3 install -r install/requirements.txt pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From ceb665972bcfc9c253397592c700a025e52bc6ec Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:15:00 +1100 Subject: Other dependencies --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a985254..313016d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install -y python3-pyqt5 python3-pip xvfb + apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus xvfb pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From 5485b8976146e63d7f9b0300aebc2d613d265b96 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:17:57 +1100 Subject: Add the other python dependencies from the BUILD.md --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 313016d7..1ad43878 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python-nautilus xvfb + apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From 7f06b7d2677ace7beb77d4046b275b9bca7ec159 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:34:34 +1100 Subject: Back to python container --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ad43878..46d1ab08 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: debian:buster + - image: circleci/python:3.6.6 working_directory: ~/repo @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb + apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy pip3 install -r install/requirements-tests.txt pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From d50adaa52c3ec5c92e71427ac483908c5a16587e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:35:30 +1100 Subject: Back to sudo --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 46d1ab08..c90a85be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,9 +18,9 @@ jobs: - run: name: install dependencies command: | - apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy - pip3 install -r install/requirements-tests.txt - pip3 install pytest-cov flake8 + sudo apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy + sudo pip3 install -r install/requirements-tests.txt + sudo pip3 install pytest-cov flake8 # run tests! - run: -- cgit v1.2.3-54-g00ecf From e0da92cc82e032e71759ecd9d9ef1ae0faf3a9ee Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:36:12 +1100 Subject: more sudo --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c90a85be..c2af3b1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: install dependencies command: | - sudo apt-get update && apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy + sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 -- cgit v1.2.3-54-g00ecf From 95ece1aac0ef2c852e8ffa05e33cd4df10034f83 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:38:15 +1100 Subject: back to installing main requirements.txt --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c2af3b1e..b8758353 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,12 +19,13 @@ jobs: name: install dependencies command: | sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy + sudo pip3 install -r install/requirements.txt sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 # run tests! - run: - name: run flask tests + name: run flake tests command: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From ecd837fc3cc04d9a848465c1b0ea791679f9f3d8 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:42:27 +1100 Subject: Tweak travis yml to run the same commands as circle just for curiosity --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec7ba912..2ae3f704 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,10 @@ python: - "nightly" # command to install dependencies install: - - sudo apt-get update && sudo apt-get install python3-pyqt5 - - pip install -r install/requirements.txt - - pip install -r install/requirements-tests.txt - - pip install pytest-cov flake8 + - sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy + - sudo pip3 install -r install/requirements.txt + - sudo pip3 install -r install/requirements-tests.txt + - sudo pip3 install pytest-cov flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From ecea986f14e19450d8d3c7d720c087c11220e831 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:42:57 +1100 Subject: Revert "Travis and CircleCI hate this simple test" This reverts commit a68a0dbabfe9a108014bed0e3fe6ae05c22b637a. --- .../local_onionshare_open_settings_dialog_test.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/local_onionshare_open_settings_dialog_test.py diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py new file mode 100644 index 00000000..61e66be2 --- /dev/null +++ b/tests/local_onionshare_open_settings_dialog_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import unittest +from PyQt5 import QtCore, QtTest + +from .GuiShareTest import GuiShareTest + +class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): + @classmethod + def setUpClass(cls): + test_settings = { + } + cls.gui = GuiShareTest.set_up(test_settings) + + @classmethod + def tearDownClass(cls): + GuiShareTest.tear_down() + + def test_gui(self): + self.run_all_common_setup_tests() + self.run_all_share_mode_setup_tests() + # Make sure we can open the settings dialog via the settings button + QtCore.QTimer.singleShot(1000, self.accept_dialog) + QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From e1d0d10019d27ed236a4c840e0b01332612f1b35 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:55:11 +1100 Subject: Remove problematic test again --- .../local_onionshare_open_settings_dialog_test.py | 26 ---------------------- 1 file changed, 26 deletions(-) delete mode 100644 tests/local_onionshare_open_settings_dialog_test.py diff --git a/tests/local_onionshare_open_settings_dialog_test.py b/tests/local_onionshare_open_settings_dialog_test.py deleted file mode 100644 index 61e66be2..00000000 --- a/tests/local_onionshare_open_settings_dialog_test.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -import unittest -from PyQt5 import QtCore, QtTest - -from .GuiShareTest import GuiShareTest - -class LocalOpenSettingsDialogTest(unittest.TestCase, GuiShareTest): - @classmethod - def setUpClass(cls): - test_settings = { - } - cls.gui = GuiShareTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - GuiShareTest.tear_down() - - def test_gui(self): - self.run_all_common_setup_tests() - self.run_all_share_mode_setup_tests() - # Make sure we can open the settings dialog via the settings button - QtCore.QTimer.singleShot(1000, self.accept_dialog) - QtTest.QTest.mouseClick(self.gui.settings_button, QtCore.Qt.LeftButton) - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3-54-g00ecf From 60e52aad3bb9a9268eb9965c571bcbfb0db1ea32 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 18 Oct 2018 17:55:26 +1100 Subject: Revert "Tweak travis yml to run the same commands as circle just for curiosity" This reverts commit ecd837fc3cc04d9a848465c1b0ea791679f9f3d8. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ae3f704..ec7ba912 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,10 @@ python: - "nightly" # command to install dependencies install: - - sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy - - sudo pip3 install -r install/requirements.txt - - sudo pip3 install -r install/requirements-tests.txt - - sudo pip3 install pytest-cov flake8 + - sudo apt-get update && sudo apt-get install python3-pyqt5 + - pip install -r install/requirements.txt + - pip install -r install/requirements-tests.txt + - pip install pytest-cov flake8 before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics -- cgit v1.2.3-54-g00ecf From 07057d6b0b780867f75353d68421e89112d80a78 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 25 Oct 2018 11:01:09 -0700 Subject: Delete obsolete dev_scripts/run_all_tests.sh script, and add xvfb-run info to the build instructions --- BUILD.md | 6 ++++++ dev_scripts/run_all_tests.sh | 14 -------------- 2 files changed, 6 insertions(+), 14 deletions(-) delete mode 100755 dev_scripts/run_all_tests.sh diff --git a/BUILD.md b/BUILD.md index b92f4110..2a055d98 100644 --- a/BUILD.md +++ b/BUILD.md @@ -156,3 +156,9 @@ pytest --runtor tests/ ``` Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive. + +You can also choose to wrap the tests in `xvfb-run` so that a ton of OnionShare windows don't pop up on your desktop (you may need to install the `xorg-x11-server-Xvfb` package), like this: + +```sh +xvfb-run pytest tests/ +``` diff --git a/dev_scripts/run_all_tests.sh b/dev_scripts/run_all_tests.sh deleted file mode 100755 index 90ef1dc0..00000000 --- a/dev_scripts/run_all_tests.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -ROOT="$( dirname $(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd ))" - -# CLI tests -cd $ROOT -pytest tests/ - -# Local GUI tests -cd $ROOT/tests_gui_local -./run_unit_tests.sh - -# Tor GUI tests -cd $ROOT/tests_gui_tor -./run_unit_tests.sh -- cgit v1.2.3-54-g00ecf From 354604dbdbb0801fe69e65d845d105eda25d375f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 25 Oct 2018 13:16:30 -0700 Subject: Remove .travis.yml because we're switching to CircleCI --- .travis.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ec7ba912..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: python -dist: trusty -sudo: required -python: - - "3.6" - - "3.6-dev" - - "3.7-dev" - - "nightly" -# command to install dependencies -install: - - sudo apt-get update && sudo apt-get install python3-pyqt5 - - pip install -r install/requirements.txt - - pip install -r install/requirements-tests.txt - - pip install pytest-cov flake8 -before_script: - # stop the build if there are Python syntax errors or undefined names - - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -# run CLI tests and local GUI tests -script: - - xvfb-run pytest --cov=onionshare --cov=onionshare_gui -vvv tests/ -- cgit v1.2.3-54-g00ecf From a16d56239a1f57373056e8b824b11d6175483016 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 25 Oct 2018 13:20:46 -0700 Subject: Update CI build status image in readme to use CircleCI instead of Travis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55487045..052aaf59 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OnionShare -[![Build Status](https://travis-ci.org/micahflee/onionshare.png)](https://travis-ci.org/micahflee/onionshare) +[![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) [OnionShare](https://onionshare.org) lets you securely and anonymously share files of any size. It works by starting a web server, making it accessible as a Tor Onion Service, and generating an unguessable URL to access and download the files. It does _not_ require setting up a separate server or using a third party file-sharing service. You host the files on your own computer and use a Tor Onion Service to make it temporarily accessible over the internet. The receiving user just needs to open the URL in Tor Browser to download the file. -- cgit v1.2.3-54-g00ecf From 6233487ecdaed7c4a678a01889703bfd0dec0787 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 25 Oct 2018 21:13:16 -0700 Subject: Receive mode puts files in a directory based on the timestamp of the upload --- onionshare/__init__.py | 2 +- onionshare/common.py | 27 --------------------------- onionshare/web/receive_mode.py | 27 ++++++++++++++------------- onionshare/web/web.py | 1 - onionshare_gui/onionshare_gui.py | 5 +---- share/locale/en.json | 1 - 6 files changed, 16 insertions(+), 47 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 069559c5..1e81333e 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -21,7 +21,7 @@ along with this program. If not, see . import os, sys, time, argparse, threading from . import strings -from .common import Common, DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable +from .common import Common from .web import Web from .onion import * from .onionshare import OnionShare diff --git a/onionshare/common.py b/onionshare/common.py index cab1e747..ffa6529f 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -32,20 +32,6 @@ import time from .settings import Settings -class DownloadsDirErrorCannotCreate(Exception): - """ - Error creating the downloads dir (~/OnionShare by default). - """ - pass - - -class DownloadsDirErrorNotWritable(Exception): - """ - Downloads dir is not writable. - """ - pass - - class Common(object): """ The Common object is shared amongst all parts of OnionShare. @@ -390,19 +376,6 @@ class Common(object): }""" } - def validate_downloads_dir(self): - """ - Validate that downloads_dir exists, and create it if it doesn't - """ - if not os.path.isdir(self.settings.get('downloads_dir')): - try: - os.mkdir(self.settings.get('downloads_dir'), 0o700) - except: - raise DownloadsDirErrorCannotCreate - - if not os.access(self.settings.get('downloads_dir'), os.W_OK): - raise DownloadsDirErrorNotWritable - @staticmethod def random_string(num_bytes, output_len=None): """ diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 4a6934a1..66e00240 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -4,7 +4,6 @@ from datetime import datetime from flask import Request, request, render_template, make_response, flash, redirect from werkzeug.utils import secure_filename -from ..common import DownloadsDirErrorCannotCreate, DownloadsDirErrorNotWritable from .. import strings @@ -59,17 +58,19 @@ class ReceiveModeWeb(object): """ Upload files. """ - # Make sure downloads_dir exists + # Make sure the receive mode dir exists + now = datetime.now() + date_dir = now.strftime("%Y-%m-%d") + time_dir = now.strftime("%H.%M:%S") + receive_mode_dir = os.path.join(self.common.settings.get('downloads_dir'), date_dir, time_dir) valid = True try: - self.common.validate_downloads_dir() - except DownloadsDirErrorCannotCreate: - self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path) - print(strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - valid = False - except DownloadsDirErrorNotWritable: - self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE, request.path) - print(strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + os.makedirs(receive_mode_dir, 0o700) + except PermissionError: + self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path, { + "receive_mode_dir": receive_mode_dir + }) + print(strings._('error_cannot_create_downloads_dir').format(receive_mode_dir)) valid = False if not valid: flash('Error uploading, please inform the OnionShare user', 'error') @@ -86,7 +87,7 @@ class ReceiveModeWeb(object): # Automatically rename the file, if a file of the same name already exists filename = secure_filename(f.filename) filenames.append(filename) - local_path = os.path.join(self.common.settings.get('downloads_dir'), filename) + local_path = os.path.join(receive_mode_dir, filename) if os.path.exists(local_path): if '.' in filename: # Add "-i", e.g. change "foo.txt" to "foo-2.txt" @@ -98,7 +99,7 @@ class ReceiveModeWeb(object): valid = False while not valid: new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + local_path = os.path.join(receive_mode_dir, new_filename) if os.path.exists(local_path): i += 1 else: @@ -109,7 +110,7 @@ class ReceiveModeWeb(object): valid = False while not valid: new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(self.common.settings.get('downloads_dir'), new_filename) + local_path = os.path.join(receive_mode_dir, new_filename) if os.path.exists(local_path): i += 1 else: diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 52c4da16..2ae011b7 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -39,7 +39,6 @@ class Web(object): REQUEST_UPLOAD_FILE_RENAMED = 7 REQUEST_UPLOAD_FINISHED = 8 REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 - REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE = 10 def __init__(self, common, is_gui, mode='share'): self.common = common diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 8c8e4e73..1e254f61 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -394,10 +394,7 @@ class OnionShareGui(QtWidgets.QMainWindow): mode.handle_request_upload_finished(event) if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_downloads_dir').format(self.common.settings.get('downloads_dir'))) - - if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_NOT_WRITABLE: - Alert(self.common, strings._('error_downloads_dir_not_writable').format(self.common.settings.get('downloads_dir'))) + Alert(self.common, strings._('error_cannot_create_downloads_dir').format(event["data"]["receive_mode_dir"])) if event["type"] == Web.REQUEST_OTHER: if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): diff --git a/share/locale/en.json b/share/locale/en.json index db416c9b..d57ef3f3 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -155,7 +155,6 @@ "info_in_progress_uploads_tooltip": "{} upload(s) in progress", "info_completed_uploads_tooltip": "{} upload(s) completed", "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", - "error_downloads_dir_not_writable": "The receive mode folder is write protected: {}", "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.

    Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", -- cgit v1.2.3-54-g00ecf From 65b4afeba34c23aa3fe856e4fadbe7f1d20c8a2d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 25 Oct 2018 21:38:20 -0700 Subject: Communicate the receive mode dir to the GUI, so clicking the open folder button opens the file manager to the correct directory --- onionshare/web/receive_mode.py | 7 +++++++ onionshare/web/web.py | 5 +++-- onionshare_gui/mode/__init__.py | 6 ++++++ onionshare_gui/mode/history.py | 13 ++++++++++++- onionshare_gui/mode/receive_mode/__init__.py | 10 ++++++++++ onionshare_gui/onionshare_gui.py | 3 +++ onionshare_gui/receive_mode/uploads.py | 12 +++++++++++- 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 66e00240..edaf8bbc 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -125,6 +125,13 @@ class ReceiveModeWeb(object): 'new_filename': basename }) + # Tell the GUI the receive mode directory for this file + self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, { + 'id': request.upload_id, + 'filename': basename, + 'dir': receive_mode_dir + }) + self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) print(strings._('receive_mode_received_file').format(local_path)) f.save(local_path) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 2ae011b7..a423b2e1 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -37,8 +37,9 @@ class Web(object): REQUEST_RATE_LIMIT = 5 REQUEST_CLOSE_SERVER = 6 REQUEST_UPLOAD_FILE_RENAMED = 7 - REQUEST_UPLOAD_FINISHED = 8 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_UPLOAD_SET_DIR = 8 + REQUEST_UPLOAD_FINISHED = 9 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 10 def __init__(self, common, is_gui, mode='share'): self.common = common diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 0971ff32..5110289f 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -324,6 +324,12 @@ class Mode(QtWidgets.QWidget): """ pass + def handle_request_upload_set_dir(self, event): + """ + Handle REQUEST_UPLOAD_SET_DIR event. + """ + pass + def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index b446b9fb..38e0fed3 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -118,6 +118,7 @@ class UploadHistoryItemFile(QtWidgets.QWidget): self.common.log('UploadHistoryItemFile', '__init__', 'filename: {}'.format(filename)) self.filename = filename + self.dir = None self.started = datetime.now() # Filename label @@ -158,13 +159,20 @@ class UploadHistoryItemFile(QtWidgets.QWidget): self.filename = new_filename self.filename_label.setText(self.filename) + def set_dir(self, dir): + self.dir = dir + def open_folder(self): """ Open the downloads folder, with the file selected, in a cross-platform manner """ self.common.log('UploadHistoryItemFile', 'open_folder') - abs_filename = os.path.join(self.common.settings.get('downloads_dir'), self.filename) + if not self.dir: + self.common.log('UploadHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + return + + abs_filename = os.path.join(self.dir, self.filename) # Linux if self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -266,6 +274,9 @@ class UploadHistoryItem(HistoryItem): self.files[data['old_filename']].rename(data['new_filename']) self.files[data['new_filename']] = self.files.pop(data['old_filename']) + elif data['action'] == 'set_dir': + self.files[data['filename']].set_dir(data['dir']) + elif data['action'] == 'finished': # Hide the progress bar self.progress_bar.hide() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index f070f963..d6c0c351 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -168,6 +168,16 @@ class ReceiveMode(Mode): 'new_filename': event["data"]["new_filename"] }) + def handle_request_upload_set_dir(self, event): + """ + Handle REQUEST_UPLOAD_SET_DIR event. + """ + self.history.update(event["data"]["id"], { + 'action': 'set_dir', + 'filename': event["data"]["filename"], + 'dir': event["data"]["dir"] + }) + def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 1e254f61..eab3261e 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -390,6 +390,9 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) + elif event["type"] == Web.REQUEST_UPLOAD_SET_DIR: + mode.handle_request_upload_set_dir(event) + elif event["type"] == Web.REQUEST_UPLOAD_FINISHED: mode.handle_request_upload_finished(event) diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py index 33d993b3..68c94b1c 100644 --- a/onionshare_gui/receive_mode/uploads.py +++ b/onionshare_gui/receive_mode/uploads.py @@ -35,6 +35,7 @@ class File(QtWidgets.QWidget): self.common.log('File', '__init__', 'filename: {}'.format(filename)) self.filename = filename + self.dir = None self.started = datetime.now() # Filename label @@ -71,6 +72,9 @@ class File(QtWidgets.QWidget): if complete: self.folder_button.show() + def set_dir(self, dir): + self.dir = dir + def rename(self, new_filename): self.filename = new_filename self.filename_label.setText(self.filename) @@ -81,7 +85,10 @@ class File(QtWidgets.QWidget): """ self.common.log('File', 'open_folder') - abs_filename = os.path.join(self.common.settings.get('downloads_dir'), self.filename) + if not self.dir: + return + + abs_filename = os.path.join(self.dir, self.filename) # Linux if self.common.platform == 'Linux' or self.common.platform == 'BSD': @@ -182,6 +189,9 @@ class Upload(QtWidgets.QWidget): self.files[old_filename].rename(new_filename) self.files[new_filename] = self.files.pop(old_filename) + def set_dir(self, filename, dir): + self.files[filename].set_dir(dir) + def finished(self): # Hide the progress bar self.progress_bar.hide() -- cgit v1.2.3-54-g00ecf From f5e0e9dd62c920f6f5b7c98636d8ee156d3ed1ce Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 26 Oct 2018 15:08:55 -0700 Subject: Fix tests so they recognize the new receive mode location --- onionshare/web/receive_mode.py | 2 +- tests/GuiReceiveTest.py | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index edaf8bbc..3149029f 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -61,7 +61,7 @@ class ReceiveModeWeb(object): # Make sure the receive mode dir exists now = datetime.now() date_dir = now.strftime("%Y-%m-%d") - time_dir = now.strftime("%H.%M:%S") + time_dir = now.strftime("%H.%M.%S") receive_mode_dir = os.path.join(self.common.settings.get('downloads_dir'), date_dir, time_dir) valid = True try: diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index a659a79f..eaed8343 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -1,10 +1,11 @@ import os import requests +from datetime import datetime, timedelta from PyQt5 import QtCore, QtTest from .GuiBaseTest import GuiBaseTest class GuiReceiveTest(GuiBaseTest): - def upload_file(self, public_mode, file_to_upload, expected_file): + def upload_file(self, public_mode, file_to_upload, expected_basename): '''Test that we can upload the file''' files = {'file[]': open(file_to_upload, 'rb')} if not public_mode: @@ -13,9 +14,23 @@ class GuiReceiveTest(GuiBaseTest): path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) response = requests.post(path, files=files) QtTest.QTest.qWait(2000) - self.assertTrue(os.path.isfile(expected_file)) - def upload_file_should_fail(self, public_mode, expected_file): + # Make sure the file is within the last 10 seconds worth of filenames + exists = False + now = datetime.now() + for i in range(10): + date_dir = now.strftime("%Y-%m-%d") + time_dir = now.strftime("%H.%M.%S") + receive_mode_dir = os.path.join(self.gui.common.settings.get('downloads_dir'), date_dir, time_dir) + expected_filename = os.path.join(receive_mode_dir, expected_basename) + if os.path.exists(expected_filename): + exists = True + break + now = now - timedelta(seconds=1) + + self.assertTrue(exists) + + def upload_file_should_fail(self, public_mode): '''Test that we can't upload the file when permissions are wrong, and expected content is shown''' files = {'file[]': open('/tmp/test.txt', 'rb')} if not public_mode: @@ -73,14 +88,14 @@ class GuiReceiveTest(GuiBaseTest): self.run_all_receive_mode_setup_tests(public_mode) if not public_mode: self.try_public_paths_in_non_public_mode() - self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test.txt') + self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') self.history_widgets_present(self.gui.receive_mode) self.counter_incremented(self.gui.receive_mode, 1) - self.upload_file(public_mode, '/tmp/test.txt', '/tmp/OnionShare/test-2.txt') + self.upload_file(public_mode, '/tmp/test.txt', 'test.txt') self.counter_incremented(self.gui.receive_mode, 2) - self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test') + self.upload_file(public_mode, '/tmp/testdir/test', 'test') self.counter_incremented(self.gui.receive_mode, 3) - self.upload_file(public_mode, '/tmp/testdir/test', '/tmp/OnionShare/test-2') + self.upload_file(public_mode, '/tmp/testdir/test', 'test') self.counter_incremented(self.gui.receive_mode, 4) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) @@ -94,7 +109,7 @@ class GuiReceiveTest(GuiBaseTest): '''Attempt to upload (unwritable) files in receive mode and stop the share''' self.run_all_receive_mode_setup_tests(public_mode) self.upload_dir_permissions(0o400) - self.upload_file_should_fail(public_mode, '/tmp/OnionShare/test.txt') + self.upload_file_should_fail(public_mode) self.server_is_stopped(self.gui.receive_mode, True) self.web_server_is_stopped() self.server_status_indicator_says_closed(self.gui.receive_mode, False) -- cgit v1.2.3-54-g00ecf From 8016010a229533b05f5ea2f1e29a12f8cd80c8e5 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Mon, 12 Nov 2018 01:35:18 +0100 Subject: Spelling: Recipient --- share/locale/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/en.json b/share/locale/en.json index d57ef3f3..b02e522f 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "You're in the process of receiving files. Are you sure you want to quit OnionShare?", "gui_quit_warning_quit": "Quit", "gui_quit_warning_dont_quit": "Cancel", - "error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the receipient a new address to share.", + "error_rate_limit": "Someone has made too many wrong attempts on your address, which means they could be trying to guess it, so OnionShare has stopped the server. Start sharing again and send the recipient a new address to share.", "zip_progress_bar_format": "Compressing: %p%", "error_stealth_not_supported": "To use client authorization, you need at least both Tor 0.2.9.1-alpha (or Tor Browser 6.5) and python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.", -- cgit v1.2.3-54-g00ecf From db8548c35bd5d0236c3083e9e860b82a1dbca808 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 13 Nov 2018 14:42:26 +1100 Subject: Try and fix closing the request for a valid upload post-timer expiry, whilst still rejecting subsequent uploads --- onionshare/web/receive_mode.py | 56 ++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 4ea95201..6ac96f8e 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -244,39 +244,38 @@ class ReceiveModeRequest(Request): if self.path == '/upload': self.upload_request = True - # Prevent new uploads if we've said so (timer expired) - if not self.web.receive_mode.can_upload: - self.upload_request = False - if self.upload_request: # A dictionary that maps filenames to the bytes uploaded so far self.progress = {} - # Create an upload_id, attach it to the request - self.upload_id = self.web.receive_mode.upload_count + # Prevent new uploads if we've said so (timer expired) + if self.web.receive_mode.can_upload: - self.web.receive_mode.upload_count += 1 + # Create an upload_id, attach it to the request + self.upload_id = self.web.receive_mode.upload_count - # Figure out the content length - try: - self.content_length = int(self.headers['Content-Length']) - except: - self.content_length = 0 + self.web.receive_mode.upload_count += 1 - print("{}: {}".format( - datetime.now().strftime("%b %d, %I:%M%p"), - strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) - )) + # Figure out the content length + try: + self.content_length = int(self.headers['Content-Length']) + except: + self.content_length = 0 - # Tell the GUI - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, - 'content_length': self.content_length - }) + print("{}: {}".format( + datetime.now().strftime("%b %d, %I:%M%p"), + strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) + )) - self.web.receive_mode.uploads_in_progress.append(self.upload_id) + # Tell the GUI + self.web.add_request(self.web.REQUEST_STARTED, self.path, { + 'id': self.upload_id, + 'content_length': self.content_length + }) - self.previous_file = None + self.web.receive_mode.uploads_in_progress.append(self.upload_id) + + self.previous_file = None def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): """ @@ -296,13 +295,16 @@ class ReceiveModeRequest(Request): Closing the request. """ super(ReceiveModeRequest, self).close() - if self.upload_request: + try: + upload_id = self.upload_id + self.web.common.log('ReceiveModeWeb', 'We finished our upload') # Inform the GUI that the upload has finished self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': self.upload_id + 'id': upload_id }) - self.web.receive_mode.uploads_in_progress.remove(self.upload_id) - + self.web.receive_mode.uploads_in_progress.remove(upload_id) + except AttributeError: + pass def file_write_func(self, filename, length): """ -- cgit v1.2.3-54-g00ecf From 09976353931eff4992428d84d27e68ba0bf27352 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 13 Nov 2018 14:59:29 +1100 Subject: remove obsolete settings in test that related to allowing receiver to shutdown service --- tests/local_onionshare_settings_dialog_test.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/local_onionshare_settings_dialog_test.py b/tests/local_onionshare_settings_dialog_test.py index 6d8923b6..c1e48122 100644 --- a/tests/local_onionshare_settings_dialog_test.py +++ b/tests/local_onionshare_settings_dialog_test.py @@ -85,11 +85,6 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): # receive mode self.gui.downloads_dir_lineedit.setText('/tmp/OnionShareSettingsTest') - # allow receiver shutdown is on - self.assertTrue(self.gui.receive_allow_receiver_shutdown_checkbox.isChecked()) - # disable receiver shutdown - QtTest.QTest.mouseClick(self.gui.receive_allow_receiver_shutdown_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.receive_allow_receiver_shutdown_checkbox.height()/2)) - self.assertFalse(self.gui.receive_allow_receiver_shutdown_checkbox.isChecked()) # bundled mode is enabled @@ -168,7 +163,6 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): self.assertTrue(data["save_private_key"]) self.assertTrue(data["use_stealth"]) self.assertEqual(data["downloads_dir"], "/tmp/OnionShareSettingsTest") - self.assertFalse(data["receive_allow_receiver_shutdown"]) self.assertFalse(data["close_after_first_download"]) self.assertEqual(data["connection_type"], "bundled") self.assertFalse(data["tor_bridges_use_obfs4"]) -- cgit v1.2.3-54-g00ecf From 17aa34edd912dd7dae95fc3d65d86c6cfb54fa83 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 13 Nov 2018 15:06:28 +1100 Subject: remove debug log --- onionshare/web/receive_mode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 274ee138..6985f38a 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -305,7 +305,6 @@ class ReceiveModeRequest(Request): super(ReceiveModeRequest, self).close() try: upload_id = self.upload_id - self.web.common.log('ReceiveModeWeb', 'We finished our upload') # Inform the GUI that the upload has finished self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { 'id': upload_id -- cgit v1.2.3-54-g00ecf From 395bc37af24f951b6e13d9d41ca0694b78edb64e Mon Sep 17 00:00:00 2001 From: coogor Date: Sun, 25 Nov 2018 15:37:50 +0100 Subject: onionshare.desktop Fixed categories and generic comment, added german translation --- install/onionshare.desktop | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/install/onionshare.desktop b/install/onionshare.desktop index fbac3660..3a55e498 100644 --- a/install/onionshare.desktop +++ b/install/onionshare.desktop @@ -1,11 +1,14 @@ [Desktop Entry] Name=OnionShare +GenericName=OnionShare Client Comment=Share a file securely and anonymously over Tor Comment[da]=Del en fil sikkert og anonymt over Tor +Comment[de]=Teile Dateien sicher und anonym über das Tor-Netzwerk Exec=/usr/bin/onionshare-gui Terminal=false Type=Application Icon=/usr/share/pixmaps/onionshare80.xpm -Categories=Network; +Categories=Network;FileTransfer; Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting; Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting; +Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting; -- cgit v1.2.3-54-g00ecf From 5f011e9ed652ab66b5449734e73478de9c51f7e8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 13:14:56 -0800 Subject: Fix bug related to persistent addresses when using v2 onion services --- onionshare/onion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 7122c208..6066f059 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -494,7 +494,7 @@ class Onion(object): # A new private key was generated and is in the Control port response. if self.settings.get('save_private_key'): if not self.settings.get('private_key'): - self.settings.set('private_key', key_content) + self.settings.set('private_key', res.private_key) if self.stealth: # Similar to the PrivateKey, the Control port only returns the ClientAuth @@ -579,7 +579,7 @@ class Onion(object): else: return (self.settings.get('socks_address'), self.settings.get('socks_port')) - def is_v2_key(key): + def is_v2_key(self, key): """ Helper function for determining if a key is RSA1024 (v2) or not. """ -- cgit v1.2.3-54-g00ecf From b1245dbba0d6cfa6663a2b3c2dca432733149407 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 26 Nov 2018 08:20:05 +1100 Subject: Run tests on multiple python versions in CircleCI --- .circleci/config.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b8758353..70fa3b7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,12 +3,18 @@ # Check https://circleci.com/docs/2.0/language-python/ for more details # version: 2 +workflows: + version: 2 + test: + jobs: + - test-3.5 + - test-3.6 + - test-3.7 + jobs: - build: + test-3.5: &test-template docker: - # specify the version you desire here - # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: circleci/python:3.6.6 + - image: circleci/python:3.5.6 working_directory: ~/repo @@ -18,7 +24,8 @@ jobs: - run: name: install dependencies command: | - sudo apt-get update && sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-cryptography python3-crypto python3-nacl python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy + sudo apt-get update + sudo apt-get install -y python3-pip python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-stdeb python3-all python-nautilus xvfb obfs4proxy sudo pip3 install -r install/requirements.txt sudo pip3 install -r install/requirements-tests.txt sudo pip3 install pytest-cov flake8 @@ -37,3 +44,12 @@ jobs: command: | xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ + test-3.6: + <<: *test-template + docker: + - image: circleci/python:3.6.6 + + test-3.7: + <<: *test-template + docker: + - image: circleci/python:3.7.1 -- cgit v1.2.3-54-g00ecf From 24a4e1567a4089982045079927cf4c114e0bc285 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 13:48:50 -0800 Subject: Update readme to include repology badge and better install instructions, and also new screenshots --- README.md | 30 +++++++++++++++++++++++------- screenshots/client.png | Bin 40602 -> 0 bytes screenshots/onionshare-receive-client.png | Bin 0 -> 59463 bytes screenshots/onionshare-receive-server.png | Bin 0 -> 68038 bytes screenshots/onionshare-share-client.png | Bin 0 -> 42423 bytes screenshots/onionshare-share-server.png | Bin 0 -> 55027 bytes 6 files changed, 23 insertions(+), 7 deletions(-) delete mode 100644 screenshots/client.png create mode 100644 screenshots/onionshare-receive-client.png create mode 100644 screenshots/onionshare-receive-server.png create mode 100644 screenshots/onionshare-share-client.png create mode 100644 screenshots/onionshare-share-server.png diff --git a/README.md b/README.md index a4ec2a87..caa4cb62 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,38 @@ # OnionShare -[![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) - [OnionShare](https://onionshare.org) lets you securely and anonymously share files of any size. It works by starting a web server, making it accessible as a Tor Onion Service, and generating an unguessable URL to access and download the files. It does _not_ require setting up a separate server or using a third party file-sharing service. You host the files on your own computer and use a Tor Onion Service to make it temporarily accessible over the internet. The receiving user just needs to open the URL in Tor Browser to download the file. ## Documentation To learn how OnionShare works, what its security properties are, and how to use it, check out the [wiki](https://github.com/micahflee/onionshare/wiki). -## Downloading Onionshare +## Downloading OnionShare + +You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). + +For Ubuntu-like distributions, you could use this PPA to get the latest version: + +``` +sudo add-apt-repository ppa:micahflee/ppa +sudo apt install -y onionshare +``` + +OnionShare also may be available in your operating system's package manager: -You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). It should be available in your package manager for Linux, and it's included by default in [Tails](https://tails.boum.org). +[![Packaging status](https://repology.org/badge/vertical-allrepos/onionshare.svg)](https://repology.org/metapackage/onionshare/versions) ## Developing OnionShare You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our developers mailing list [here](https://lists.riseup.net/www/info/onionshare-dev). -# Screenshots +Test status: [![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) + +# Screenshots + +![Share mode OnionShare](/screenshots/onionshare-share-server.png) + +![Share mode Tor Browser](/screenshots/onionshare-share-client.png) + +![Receive mode OnionShare](/screenshots/onionshare-receive-server.png) -![Server Screenshot](/screenshots/appdata-server.png) -![Client Screenshot](/screenshots/client.png) +![Receive mode Tor Browser](/screenshots/onionshare-receive-client.png) diff --git a/screenshots/client.png b/screenshots/client.png deleted file mode 100644 index fdf69fd0..00000000 Binary files a/screenshots/client.png and /dev/null differ diff --git a/screenshots/onionshare-receive-client.png b/screenshots/onionshare-receive-client.png new file mode 100644 index 00000000..7702a4bd Binary files /dev/null and b/screenshots/onionshare-receive-client.png differ diff --git a/screenshots/onionshare-receive-server.png b/screenshots/onionshare-receive-server.png new file mode 100644 index 00000000..0962a74f Binary files /dev/null and b/screenshots/onionshare-receive-server.png differ diff --git a/screenshots/onionshare-share-client.png b/screenshots/onionshare-share-client.png new file mode 100644 index 00000000..7cb6c3c8 Binary files /dev/null and b/screenshots/onionshare-share-client.png differ diff --git a/screenshots/onionshare-share-server.png b/screenshots/onionshare-share-server.png new file mode 100644 index 00000000..76830876 Binary files /dev/null and b/screenshots/onionshare-share-server.png differ -- cgit v1.2.3-54-g00ecf From af637bc697686b1090d6d5d5791675d4b21e0c74 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 15:13:15 -0800 Subject: Adding a .entitlements file to begin testing macOS sandbox --- install/OnionShare.entitlements | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 install/OnionShare.entitlements diff --git a/install/OnionShare.entitlements b/install/OnionShare.entitlements new file mode 100644 index 00000000..fa6a9141 --- /dev/null +++ b/install/OnionShare.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + /OnionShare + + + -- cgit v1.2.3-54-g00ecf From dac480f212b00d31a1c3ff2ca367dd1e86c5d028 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 15:14:29 -0800 Subject: Use entitlements file when codesigning for macOS --- install/build_osx.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/build_osx.sh b/install/build_osx.sh index f6b27d9b..ad096139 100755 --- a/install/build_osx.sh +++ b/install/build_osx.sh @@ -25,7 +25,7 @@ if [ "$1" = "--release" ]; then IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee" echo "Codesigning the app bundle" - codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH" + codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH" --entitlements "$ROOT/install/OnionShare.entitlements" echo "Creating an installer" productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH" -- cgit v1.2.3-54-g00ecf From 0794d7fb9040e3ecf599202c27c191215a795f37 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 15:52:36 -0800 Subject: Split entitlements files into child and parent, and try using them both --- install/OnionShare.entitlements | 16 ---------------- install/build_osx.sh | 5 ++++- install/macos_sandbox/child.plist | 10 ++++++++++ install/macos_sandbox/parent.plist | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 17 deletions(-) delete mode 100644 install/OnionShare.entitlements create mode 100644 install/macos_sandbox/child.plist create mode 100644 install/macos_sandbox/parent.plist diff --git a/install/OnionShare.entitlements b/install/OnionShare.entitlements deleted file mode 100644 index fa6a9141..00000000 --- a/install/OnionShare.entitlements +++ /dev/null @@ -1,16 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - com.apple.security.network.client - - com.apple.security.temporary-exception.files.home-relative-path.read-write - - /OnionShare - - - diff --git a/install/build_osx.sh b/install/build_osx.sh index ad096139..010e3edb 100755 --- a/install/build_osx.sh +++ b/install/build_osx.sh @@ -23,9 +23,12 @@ if [ "$1" = "--release" ]; then PKG_PATH="$ROOT/dist/OnionShare.pkg" IDENTITY_NAME_APPLICATION="Developer ID Application: Micah Lee" IDENTITY_NAME_INSTALLER="Developer ID Installer: Micah Lee" + ENTITLEMENTS_CHILD_PATH="$ROOT/install/macos_sandbox/child.plist" + ENTITLEMENTS_PARENT_PATH="$ROOT/install/macos_sandbox/parent.plist" echo "Codesigning the app bundle" - codesign --deep -s "$IDENTITY_NAME_APPLICATION" "$APP_PATH" --entitlements "$ROOT/install/OnionShare.entitlements" + codesign --deep -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_CHILD_PATH" "$APP_PATH" + codesign -s "$IDENTITY_NAME_APPLICATION" -f --entitlements "$ENTITLEMENTS_PARENT_PATH" "$APP_PATH" echo "Creating an installer" productbuild --sign "$IDENTITY_NAME_INSTALLER" --component "$APP_PATH" /Applications "$PKG_PATH" diff --git a/install/macos_sandbox/child.plist b/install/macos_sandbox/child.plist new file mode 100644 index 00000000..06d88f66 --- /dev/null +++ b/install/macos_sandbox/child.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist new file mode 100644 index 00000000..ceecd30a --- /dev/null +++ b/install/macos_sandbox/parent.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-only + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + /OnionShare + + + -- cgit v1.2.3-54-g00ecf From d8b873a20825404ea8b9c6727680911b2e19e910 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 16:15:27 -0800 Subject: Create a group container for macOS sandbox, and on Mac put the Tor data dir in it --- install/macos_sandbox/parent.plist | 6 ++++++ onionshare/onion.py | 7 ++++++- onionshare/settings.py | 6 +----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist index ceecd30a..6d557cf0 100644 --- a/install/macos_sandbox/parent.plist +++ b/install/macos_sandbox/parent.plist @@ -4,6 +4,8 @@ com.apple.security.app-sandbox + com.apple.security.network.server + com.apple.security.network.client com.apple.security.files.user-selected.read-only @@ -12,5 +14,9 @@ /OnionShare + com.apple.security.application-groups + + com.micahflee.onionshare + diff --git a/onionshare/onion.py b/onionshare/onion.py index 6066f059..f9f551c4 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -166,7 +166,12 @@ class Onion(object): raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported')) # Create a torrc for this session - self.tor_data_directory = tempfile.TemporaryDirectory() + if self.common.platform == 'Darwin': + group_container_dir = os.path.expanduser('~/Library/Group Containers/com.micahflee.onionshare') + os.makedirs(group_container_dir, exist_ok=True) + self.tor_data_directory = tempfile.TemporaryDirectory(dir=group_container_dir) + else: + self.tor_data_directory = tempfile.TemporaryDirectory() if self.common.platform == 'Windows': # Windows needs to use network ports, doesn't support unix sockets diff --git a/onionshare/settings.py b/onionshare/settings.py index ed827cbd..4a056989 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -163,11 +163,7 @@ class Settings(object): Save settings to file. """ self.common.log('Settings', 'save') - - try: - os.makedirs(os.path.dirname(self.filename)) - except: - pass + os.makedirs(os.path.dirname(self.filename), exist_ok=True) open(self.filename, 'w').write(json.dumps(self._settings)) self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename)) -- cgit v1.2.3-54-g00ecf From 845d7f85647b8f2cb5757e86b3269657bf371b53 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 16:26:54 -0800 Subject: Display tor_data_directory as debug log --- onionshare/onion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/onionshare/onion.py b/onionshare/onion.py index f9f551c4..ec81a894 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -170,6 +170,7 @@ class Onion(object): group_container_dir = os.path.expanduser('~/Library/Group Containers/com.micahflee.onionshare') os.makedirs(group_container_dir, exist_ok=True) self.tor_data_directory = tempfile.TemporaryDirectory(dir=group_container_dir) + self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory)) else: self.tor_data_directory = tempfile.TemporaryDirectory() -- cgit v1.2.3-54-g00ecf From 2cbe8979abe549150513cd7489cf3e2e7b348471 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 16:50:20 -0800 Subject: Make macOS use Tor control ports instead of unix sockets to connect to the Tor controller --- install/pyinstaller.spec | 1 - onionshare/onion.py | 41 +++++++++++++++++++++-------------------- share/torrc_template | 1 - share/torrc_template-windows | 9 --------- 4 files changed, 21 insertions(+), 31 deletions(-) delete mode 100644 share/torrc_template-windows diff --git a/install/pyinstaller.spec b/install/pyinstaller.spec index 6811997b..24664bf9 100644 --- a/install/pyinstaller.spec +++ b/install/pyinstaller.spec @@ -15,7 +15,6 @@ a = Analysis( ('../share/torrc_template', 'share'), ('../share/torrc_template-obfs4', 'share'), ('../share/torrc_template-meek_lite_azure', 'share'), - ('../share/torrc_template-windows', 'share'), ('../share/images/*', 'share/images'), ('../share/locale/*', 'share/locale'), ('../share/static/*', 'share/static'), diff --git a/onionshare/onion.py b/onionshare/onion.py index ec81a894..4a94f2ce 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -170,36 +170,36 @@ class Onion(object): group_container_dir = os.path.expanduser('~/Library/Group Containers/com.micahflee.onionshare') os.makedirs(group_container_dir, exist_ok=True) self.tor_data_directory = tempfile.TemporaryDirectory(dir=group_container_dir) - self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory)) + self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name)) else: self.tor_data_directory = tempfile.TemporaryDirectory() - if self.common.platform == 'Windows': - # Windows needs to use network ports, doesn't support unix sockets - torrc_template = open(self.common.get_resource_path('torrc_template-windows')).read() + # Create the torrc + with open(self.common.get_resource_path('torrc_template')) as f: + torrc_template = f.read() + self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') + try: + self.tor_socks_port = self.common.get_available_port(1000, 65535) + except: + raise OSError(strings._('no_available_port')) + self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') + + if self.common.platform == 'Windows' or self.common.platform == "Darwin": + # Windows doesn't support unix sockets, so it must use a network port. + # macOS can't use unix sockets either because socket filenames are limited to + # 100 chars, and the macOS sandbox forces us to put the socket file in a place + # with a really long path. + torrc_template += 'ControlPort {{control_port}}\n' try: self.tor_control_port = self.common.get_available_port(1000, 65535) except: raise OSError(strings._('no_available_port')) self.tor_control_socket = None - self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') - try: - self.tor_socks_port = self.common.get_available_port(1000, 65535) - except: - raise OSError(strings._('no_available_port')) - self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') else: - # Linux, Mac and BSD can use unix sockets - with open(self.common.get_resource_path('torrc_template')) as f: - torrc_template = f.read() + # Linux and BSD can use unix sockets + torrc_template += 'ControlSocket {{control_socket}}\n' self.tor_control_port = None self.tor_control_socket = os.path.join(self.tor_data_directory.name, 'control_socket') - self.tor_cookie_auth_file = os.path.join(self.tor_data_directory.name, 'cookie') - try: - self.tor_socks_port = self.common.get_available_port(1000, 65535) - except: - raise OSError(strings._('no_available_port')) - self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc') torrc_template = torrc_template.replace('{{data_directory}}', self.tor_data_directory.name) torrc_template = torrc_template.replace('{{control_port}}', str(self.tor_control_port)) @@ -208,6 +208,7 @@ class Onion(object): torrc_template = torrc_template.replace('{{geo_ip_file}}', self.tor_geo_ip_file_path) torrc_template = torrc_template.replace('{{geo_ipv6_file}}', self.tor_geo_ipv6_file_path) torrc_template = torrc_template.replace('{{socks_port}}', str(self.tor_socks_port)) + with open(self.tor_torrc, 'w') as f: f.write(torrc_template) @@ -246,7 +247,7 @@ class Onion(object): # Connect to the controller try: - if self.common.platform == 'Windows': + if self.common.platform == 'Windows' or self.common.platform == "Darwin": self.c = Controller.from_port(port=self.tor_control_port) self.c.authenticate() else: diff --git a/share/torrc_template b/share/torrc_template index 464adf32..8ac9e1ef 100644 --- a/share/torrc_template +++ b/share/torrc_template @@ -1,6 +1,5 @@ DataDirectory {{data_directory}} SocksPort {{socks_port}} -ControlSocket {{control_socket}} CookieAuthentication 1 CookieAuthFile {{cookie_auth_file}} AvoidDiskWrites 1 diff --git a/share/torrc_template-windows b/share/torrc_template-windows deleted file mode 100644 index 38a5bf1e..00000000 --- a/share/torrc_template-windows +++ /dev/null @@ -1,9 +0,0 @@ -DataDirectory {{data_directory}} -SocksPort {{socks_port}} -ControlPort {{control_port}} -CookieAuthentication 1 -CookieAuthFile {{cookie_auth_file}} -AvoidDiskWrites 1 -Log notice stdout -GeoIPFile {{geo_ip_file}} -GeoIPv6File {{geo_ipv6_file}} -- cgit v1.2.3-54-g00ecf From 66e50c96b8f63e82edd1bc0a69cc6f62f3ec1728 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 17:00:02 -0800 Subject: Fix indenting in entitlements plist, and give OnionShare read-only access to Tor Browser's data, so it can optionally use Tor Browser's tor --- install/macos_sandbox/parent.plist | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist index 6d557cf0..0e4505d5 100644 --- a/install/macos_sandbox/parent.plist +++ b/install/macos_sandbox/parent.plist @@ -10,13 +10,17 @@ com.apple.security.files.user-selected.read-only + com.apple.security.temporary-exception.files.home-relative-path.read-only + + /Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie + com.apple.security.temporary-exception.files.home-relative-path.read-write - /OnionShare - + /OnionShare + com.apple.security.application-groups - - com.micahflee.onionshare - + + com.micahflee.onionshare + -- cgit v1.2.3-54-g00ecf From 49340321445e9b5dcbcded52f72dd16b02dc7a39 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 17:17:56 -0800 Subject: Make tor data dir always be a tempdir inside OnionShare's data dir --- onionshare/common.py | 17 +++++++++++++++++ onionshare/onion.py | 9 ++------- onionshare/settings.py | 14 +------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index ffa6529f..6b3d85c4 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -123,6 +123,23 @@ class Common(object): return (tor_path, tor_geo_ip_file_path, tor_geo_ipv6_file_path, obfs4proxy_file_path) + def build_data_dir(self): + """ + Returns the path of the OnionShare data directory. + """ + if self.platform == 'Windows': + try: + appdata = os.environ['APPDATA'] + return '{}\\OnionShare'.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') + elif self.platform == 'Darwin': + return os.path.expanduser('~/Library/Application Support/OnionShare') + else: + return os.path.expanduser('~/.config/onionshare') + def build_slug(self): """ Returns a random string made from two words from the wordlist, such as "deter-trig". diff --git a/onionshare/onion.py b/onionshare/onion.py index 4a94f2ce..7b4f1daa 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -166,13 +166,8 @@ class Onion(object): raise BundledTorNotSupported(strings._('settings_error_bundled_tor_not_supported')) # Create a torrc for this session - if self.common.platform == 'Darwin': - group_container_dir = os.path.expanduser('~/Library/Group Containers/com.micahflee.onionshare') - os.makedirs(group_container_dir, exist_ok=True) - self.tor_data_directory = tempfile.TemporaryDirectory(dir=group_container_dir) - self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name)) - else: - self.tor_data_directory = tempfile.TemporaryDirectory() + self.tor_data_directory = tempfile.TemporaryDirectory(dir=self.common.build_data_dir()) + self.common.log('Onion', 'connect', 'tor_data_directory={}'.format(self.tor_data_directory.name)) # Create the torrc with open(self.common.get_resource_path('torrc_template')) as f: diff --git a/onionshare/settings.py b/onionshare/settings.py index 4a056989..058557e9 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -120,19 +120,7 @@ class Settings(object): """ Returns the path of the settings file. """ - p = platform.system() - if p == 'Windows': - try: - appdata = os.environ['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': - return os.path.expanduser('~/Library/Application Support/OnionShare/onionshare.json') - else: - return os.path.expanduser('~/.config/onionshare/onionshare.json') + return os.path.join(self.common.build_data_dir(), 'onionshare.json') def build_default_downloads_dir(self): """ -- cgit v1.2.3-54-g00ecf From 29d2518911e79f13a5b359f1a42919634ec906e7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 17:18:55 -0800 Subject: Add comments to entitlements file, and remove the application group --- install/macos_sandbox/parent.plist | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist index 0e4505d5..35b4447c 100644 --- a/install/macos_sandbox/parent.plist +++ b/install/macos_sandbox/parent.plist @@ -4,23 +4,30 @@ com.apple.security.app-sandbox + com.apple.security.network.server com.apple.security.network.client - com.apple.security.files.user-selected.read-only + + com.apple.security.files.user-selected.read-write + + com.apple.security.temporary-exception.files.absolute-path.read-only + + /private/etc/apache2/mime.types + + com.apple.security.temporary-exception.files.home-relative-path.read-only /Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie + com.apple.security.temporary-exception.files.home-relative-path.read-write /OnionShare - com.apple.security.application-groups - - com.micahflee.onionshare - -- cgit v1.2.3-54-g00ecf From 250dbf91993a4a90a19c33cbff88520ac8660753 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 17:34:06 -0800 Subject: Make sure downloads_dir exists after loading settings --- onionshare/settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/onionshare/settings.py b/onionshare/settings.py index 058557e9..41fcc536 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -146,6 +146,12 @@ class Settings(object): except: pass + # Make sure downloads_dir exists + try: + os.makedirs(self.get('downloads_dir'), exist_ok=True) + except: + pass + def save(self): """ Save settings to file. -- cgit v1.2.3-54-g00ecf From 33bd7b4a80738e50dbb810086120635db18876e2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 17:48:11 -0800 Subject: Build downloads_dir a different way in macOS, so it's correct despite the sandbox --- onionshare/settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 41fcc536..ca3fccfa 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -22,6 +22,7 @@ import json import os import platform import locale +import pwd from . import strings @@ -128,7 +129,13 @@ class Settings(object): """ # TODO: Test in Windows, though it looks like it should work # https://docs.python.org/3/library/os.path.html#os.path.expanduser - return os.path.expanduser('~/OnionShare') + if self.common.platform == "Darwin": + # We can't use os.path.expanduser in macOS because in the sandbox it + # returns the path to the sandboxed homedir + real_homedir = pwd.getpwuid(os.getuid()).pw_dir + return os.path.join(real_homedir, 'OnionShare') + else: + return os.path.expanduser('~/OnionShare') def load(self): """ -- cgit v1.2.3-54-g00ecf From db93734ed29cb37dece94ea26f9ffbe7574d82b7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 18:00:04 -0800 Subject: Add trailing slash to ~/OnionShare/ in entitlements file, so the sandbox knows it's a directory and not a file --- install/macos_sandbox/parent.plist | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/install/macos_sandbox/parent.plist b/install/macos_sandbox/parent.plist index 35b4447c..3929abe9 100644 --- a/install/macos_sandbox/parent.plist +++ b/install/macos_sandbox/parent.plist @@ -2,32 +2,38 @@ + com.apple.security.app-sandbox - + + com.apple.security.network.server com.apple.security.network.client + com.apple.security.files.user-selected.read-write + com.apple.security.temporary-exception.files.absolute-path.read-only /private/etc/apache2/mime.types + com.apple.security.temporary-exception.files.home-relative-path.read-only /Library/Application Support/TorBrowser-Data/Tor/control_auth_cookie + com.apple.security.temporary-exception.files.home-relative-path.read-write - /OnionShare + /OnionShare/ -- cgit v1.2.3-54-g00ecf From 01baf3d6fde55a83368a85ace8632079ceb61c25 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 25 Nov 2018 19:10:19 -0800 Subject: Fix test for Windows settings path --- tests/test_onionshare_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index 371b2d27..42bf7f12 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -176,7 +176,7 @@ class TestSettings: platform_windows): monkeypatch.setenv('APPDATA', 'C:') obj = settings.Settings(common.Common()) - assert obj.filename == 'C:\\OnionShare\\onionshare.json' + assert obj.filename.replace('/', '\\') == 'C:\\OnionShare\\onionshare.json' def test_set_custom_bridge(self, settings_obj): settings_obj.set('tor_bridges_use_custom_bridges', 'Bridge 45.3.20.65:9050 21300AD88890A49C429A6CB9959CFD44490A8F6E') -- cgit v1.2.3-54-g00ecf From 8182d24d553361fa60dc6ff2156a77bb3bec6f48 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 27 Nov 2018 10:50:11 +1100 Subject: Remove obsolete Download/Upload code that crept back in via an earlier merge --- onionshare_gui/receive_mode/uploads.py | 330 --------------------------------- onionshare_gui/share_mode/downloads.py | 163 ---------------- 2 files changed, 493 deletions(-) delete mode 100644 onionshare_gui/receive_mode/uploads.py delete mode 100644 onionshare_gui/share_mode/downloads.py diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py deleted file mode 100644 index 68c94b1c..00000000 --- a/onionshare_gui/receive_mode/uploads.py +++ /dev/null @@ -1,330 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -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.dir = None - 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 set_dir(self, dir): - self.dir = dir - - 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') - - if not self.dir: - return - - abs_filename = os.path.join(self.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 set_dir(self, filename, dir): - self.files[filename].set_dir(dir) - - 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 diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py deleted file mode 100644 index fe1e91b8..00000000 --- a/onionshare_gui/share_mode/downloads.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee - -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 . -""" -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()) -- cgit v1.2.3-54-g00ecf From f32af3ca7e60378b7a741fbfd0c084a9f206b729 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 26 Nov 2018 17:14:44 -0800 Subject: Update pip dependencies --- install/requirements-tests.txt | 6 +++--- install/requirements.txt | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/install/requirements-tests.txt b/install/requirements-tests.txt index 57e98ce1..89f37572 100644 --- a/install/requirements-tests.txt +++ b/install/requirements-tests.txt @@ -1,10 +1,10 @@ atomicwrites==1.2.1 attrs==18.2.0 more-itertools==4.3.0 -pluggy==0.7.1 +pluggy==0.8.0 py==1.7.0 -pytest==3.8.2 +pytest==4.0.1 pytest-faulthandler==1.5.0 pytest-qt==3.2.1 six==1.11.0 -urllib3==1.23 +urllib3==1.24.1 diff --git a/install/requirements.txt b/install/requirements.txt index 20811f7b..81430398 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -1,26 +1,26 @@ altgraph==0.16.1 asn1crypto==0.24.0 -certifi==2018.8.24 +certifi==2018.10.15 cffi==1.11.5 chardet==3.0.4 -click==6.7 -cryptography==2.3.1 +Click==7.0 +cryptography==2.4.2 Flask==1.0.2 -future==0.16.0 +future==0.17.1 idna==2.7 -itsdangerous==0.24 +itsdangerous==1.1.0 Jinja2==2.10 macholib==1.11 -MarkupSafe==1.0 +MarkupSafe==1.1.0 pefile==2018.8.8 -pycparser==2.18 -pycryptodome==3.6.6 +pycparser==2.19 +pycryptodome==3.7.2 PyInstaller==3.4 -PyQt5==5.11.2 -PyQt5-sip==4.19.12 +PyQt5==5.11.3 +PyQt5-sip==4.19.13 PySocks==1.6.8 -requests==2.19.1 +requests==2.20.1 six==1.11.0 stem==1.7.0 -urllib3==1.23 +urllib3==1.24.1 Werkzeug==0.14.1 -- cgit v1.2.3-54-g00ecf From 3a0c8dc32317d4b66230663c1c3a40f6969d9275 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 27 Nov 2018 12:10:16 -0800 Subject: In macOS, split "Add" button into "Add Files" and "Add Folder" buttons --- onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/mode/share_mode/file_selection.py | 51 +++++++++++++++++++++--- onionshare_gui/widgets.py | 4 ++ share/locale/en.json | 2 + 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 436d42f7..0cc00f92 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -47,7 +47,7 @@ class ShareMode(Mode): self.web = Web(self.common, True, 'share') # File selection - self.file_selection = FileSelection(self.common) + self.file_selection = FileSelection(self.common, self) if self.filenames: for filename in self.filenames: self.file_selection.file_list.add_file(filename) diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/share_mode/file_selection.py index ec3b5ea5..0d4229fe 100644 --- a/onionshare_gui/mode/share_mode/file_selection.py +++ b/onionshare_gui/mode/share_mode/file_selection.py @@ -288,10 +288,11 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ - def __init__(self, common): + def __init__(self, common, parent): super(FileSelection, self).__init__() self.common = common + self.parent = parent self.server_on = False @@ -302,13 +303,25 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) - self.add_button.clicked.connect(self.add) + if self.common.platform == 'Darwin': + # The macOS sandbox makes it so the Mac version needs separate add files + # and folders buttons, in order to use native file selection dialogs + self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button.clicked.connect(self.add_files) + self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button.clicked.connect(self.add_folder) + else: + self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button.clicked.connect(self.add) self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() - button_layout.addWidget(self.add_button) + if self.common.platform == 'Darwin': + button_layout.addWidget(self.add_files_button) + button_layout.addWidget(self.add_folder_button) + else: + button_layout.addWidget(self.add_button) button_layout.addWidget(self.delete_button) # Add the widgets @@ -323,10 +336,18 @@ class FileSelection(QtWidgets.QVBoxLayout): """ # All buttons should be hidden if the server is on if self.server_on: - self.add_button.hide() + if self.common.platform == 'Darwin': + self.add_files_button.hide() + self.add_folder_button.hide() + else: + self.add_button.hide() self.delete_button.hide() else: - self.add_button.show() + if self.common.platform == 'Darwin': + self.add_files_button.show() + self.add_folder_button.show() + else: + self.add_button.show() # Delete button should be hidden if item isn't selected if len(self.file_list.selectedItems()) == 0: @@ -349,6 +370,24 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.setCurrentItem(None) self.update() + def add_files(self): + """ + Add files button clicked. + """ + files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + filenames = files[0] + for filename in filenames: + self.file_list.add_file(filename) + + def add_folder(self): + """ + Add folder button clicked. + """ + filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, + caption=strings._('gui_choose_items'), + options=QtWidgets.QFileDialog.ShowDirsOnly) + self.file_list.add_file(filename) + def delete(self): """ Delete button clicked diff --git a/onionshare_gui/widgets.py b/onionshare_gui/widgets.py index eaa5904d..600165aa 100644 --- a/onionshare_gui/widgets.py +++ b/onionshare_gui/widgets.py @@ -44,6 +44,10 @@ class AddFileDialog(QtWidgets.QFileDialog): """ Overridden version of QFileDialog which allows us to select folders as well as, or instead of, files. For adding files/folders to share. + + Note that this dialog can't be used in macOS, only in Windows, Linux, and BSD. + This is because the macOS sandbox requires native dialogs, and this is a Qt5 + dialog. """ def __init__(self, common, *args, **kwargs): QtWidgets.QFileDialog.__init__(self, *args, **kwargs) diff --git a/share/locale/en.json b/share/locale/en.json index b02e522f..438274ef 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -33,6 +33,8 @@ "help_config": "Custom JSON config file location (optional)", "gui_drag_and_drop": "Drag and drop files and folders\nto start sharing", "gui_add": "Add", + "gui_add_files": "Add Files", + "gui_add_folder": "Add Folder", "gui_delete": "Delete", "gui_choose_items": "Choose", "gui_share_start_server": "Start sharing", -- cgit v1.2.3-54-g00ecf From d15e00061a4488aa3ac513671a99ce1213f90a51 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 5 Dec 2018 18:19:35 +1100 Subject: Keep the upload running in the GUI if the timer has run out --- onionshare_gui/mode/receive_mode/__init__.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index d6c0c351..c53f1ea1 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -96,8 +96,16 @@ class ReceiveMode(Mode): """ The shutdown timer expired, should we stop the server? Returns a bool """ - # TODO: wait until the final upload is done before stoppign the server? - return True + # If there were no attempts to upload files, or all uploads are done, we can stop + if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress: + self.server_status.stop_server() + self.server_status_label.setText(strings._('close_on_timeout')) + return True + # An upload is probably still running - hold off on stopping the share, but block new shares. + else: + self.server_status_label.setText(strings._('timeout_upload_still_running')) + self.web.receive_mode.can_upload = False + return False def start_server_custom(self): """ -- cgit v1.2.3-54-g00ecf From f1d0c66768f1f837f5c1b3e31f6d85a4624572e8 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Tue, 25 Sep 2018 17:35:20 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) diff --git a/share/locale/no.json b/share/locale/no.json index a04710d2..4b3fd97e 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,7 +1,184 @@ { - "give_this_url": "Gi personen du vil sende filen til denne URL-en:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", + "give_this_url": "Gi denne adressen til mottakeren:", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "Kopierte URL-en til utklippstavlen", - "other_page_loaded": "En annen side har blitt lastet" + "other_page_loaded": "En annen side har blitt lastet", + "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.", + "preparing_files": "Pakker sammen filer.", + "give_this_url_stealth": "Gi denne adressen og HidServAuth-linjen til mottakeren:", + "give_this_url_receive": "Gi denne adressen til avsenderen:", + "give_this_url_receive_stealth": "Gi denne adressen og HidServAuth-linjen til avsenderen:", + "not_a_readable_file": "{0:s} er ikke en lesbar fil.", + "no_available_port": "Fant ikke tilgjengelig port for oppstart av løktjenesten", + "close_on_timeout": "Stoppet fordi tidsavbruddsuret gikk ut", + "closing_automatically": "Stoppet fordi nedlasting fullførtes", + "timeout_download_still_running": "Venter på at nedlastingen skal fullføres", + "large_filesize": "Advarsel: forsendelse av stor deling kan ta timer", + "systray_menu_exit": "Avslutt", + "systray_download_started_title": "OnionShare-nedlasting startet", + "systray_download_started_message": "En bruker startet nedlasting av filene dine", + "systray_download_completed_title": "OnionShare-nedlasting fullført", + "systray_download_completed_message": "Brukeren fullførte nedlasting av filene dine", + "systray_download_canceled_title": "OnionShare-nedlasting avbrutt", + "systray_download_canceled_message": "Brukeren avbrøt nedlastingen", + "systray_upload_started_title": "OnionShare-opplasting startet", + "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin", + "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)", + "help_stay_open": "Fortsett å dele etter første nedlasting", + "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder", + "help_stealth": "Bruk klientidentifisering (avansert)", + "help_receive": "Motta delinger istedenfor å sende dem", + "help_debug": "Log OnionShare-feil til stdout, og vev-feil til disk", + "help_filename": "Liste over filer eller mapper å dele", + "help_config": "Egendefinert JSON-oppsettsfil (valgfri)", + "gui_drag_and_drop": "Dra og slipp filer og mapper\nfor å starte deling", + "gui_add": "Legg til", + "gui_delete": "Slett", + "gui_choose_items": "Velg", + "gui_share_start_server": "Start deling", + "gui_share_stop_server": "Stopp deling", + "gui_share_stop_server_shutdown_timeout": "Stopp deling ({}s gjenstår)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}", + "gui_receive_start_server": "Start mottaksmodus", + "gui_receive_stop_server": "Stopp mottaksmodus", + "gui_receive_stop_server_shutdown_timeout": "Stopp mottaksmodus ({}s gjenstår)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}", + "gui_copy_url": "Kopier nettadresse", + "gui_copy_hidservauth": "Kopier HidServAuth", + "gui_downloads": "Nedlastingshistorikk", + "gui_no_downloads": "Ingen nedlastinger enda.", + "gui_canceled": "Avbrutt", + "gui_copied_url_title": "Kopierte OnionShare-adressen", + "gui_copied_hidservauth_title": "Kopierte HidServAuth-linje", + "gui_copied_hidservauth": "HidServAuth-linje kopiert til utklippstavle", + "gui_please_wait": "Starter… Klikk for å avbryte", + "gui_download_upload_progress_complete": "%p%, {0:s} forløpt.", + "gui_download_upload_progress_starting": "{0:s}, %p% (regner ut)", + "gui_download_upload_progress_eta": "{0:s}, anslått ferdigstilt: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Hold an", + "gui_share_quit_warning": "Filer er i ferd med å bli sendt. Er du sikker på at du ønsker å avslutte OnionShare?", + "gui_receive_quit_warning": "Du har ikke fått overført alle filene til deg enda. Er du sikker på at du ønsker å avslutte OnionShare?", + "gui_quit_warning_quit": "Avslutt", + "gui_quit_warning_dont_quit": "Avbryt", + "error_rate_limit": "Noen har prøvd adressen din for mange ganger, som kan betyr at de prøver å gjette seg fram til den, OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.", + "zip_progress_bar_format": "Pakker sammen: %p%", + "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-nettleseren 6.5) og python3-stem 1.5.0", + "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", + "gui_settings_window_title": "Innstillinger", + "gui_settings_whats_this": "Hva er dette?", + "gui_settings_stealth_option": "Bruk klientidentifisering (foreldet)", + "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.", + "gui_settings_autoupdate_label": "Se etter ny versjon", + "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig", + "gui_settings_autoupdate_timestamp": "Sist sjekket: {}", + "gui_settings_autoupdate_timestamp_never": "Aldri", + "gui_settings_autoupdate_check_button": "Se etter ny versjon", + "gui_settings_general_label": "Hovedinnstillinger", + "gui_settings_sharing_label": "Delingsinnstillinger", + "gui_settings_close_after_first_download_option": "Stopp deling etter første nedlasting", + "gui_settings_connection_type_label": "Hvordan skal OnionShare koble seg til Tor?", + "gui_settings_connection_type_bundled_option": "Bruk Tor-versjonen som kommer med OnionShare", + "gui_settings_connection_type_automatic_option": "Forsøk automatisk oppsett med Tor-nettleser", + "gui_settings_connection_type_control_port_option": "Koble til ved bruk av kontrollport", + "gui_settings_connection_type_socket_file_option": "Koble til ved bruk av socket-fil", + "gui_settings_connection_type_test_button": "Test tilkobling til Tor", + "gui_settings_control_port_label": "Kontrollport", + "gui_settings_socket_file_label": "Socket-fil", + "gui_settings_socks_label": "SOCKS-port", + "gui_settings_authenticate_label": "Tor-identitetsbekreftelsesinnstillinger", + "gui_settings_authenticate_no_auth_option": "Ingen identitetsbekreftelse, eller kakeidentifiseringsbekreftelse", + "gui_settings_authenticate_password_option": "Passord", + "gui_settings_password_label": "Passord", + "gui_settings_tor_bridges": "Støtte for Tor-bro", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ikke benytt broer", + "gui_settings_tor_bridges_obfs4_radio_option": "Bruk innebygd pluggbare obfs4-transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare obfs4-transporter (krever obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bruk innebygd pluggbare meek_lite (Azure) transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare meek_lite (Azure) transporter (krever obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advarsel: Meek-lite-broene er veldig kostbare å kjøre for Tor-prosjektet.

    Kun bruk dem hvis direkte tilkobling til Tor ikke virker, via obfs-transporter, eller andre normale broer.", + "gui_settings_tor_bridges_custom_radio_option": "Bruk egendefinerte broer", + "gui_settings_tor_bridges_custom_label": "Du kan hente broer fra https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ingen av broene du la til virker.\nDobbeltsjekk dem eller legg til andre.", + "gui_settings_button_save": "Lagre", + "gui_settings_button_cancel": "Avbryt", + "gui_settings_button_help": "Hjelp", + "gui_settings_shutdown_timeout_checkbox": "Bruk tidsavbruddsur", + "gui_settings_shutdown_timeout": "Stopp deling ved:", + "settings_saved": "Innstillinger lagret i {}", + "settings_error_unknown": "Kan ikke koble til Tor-kontroller fordi innstillingene dine ikke gir mening.", + "settings_error_automatic": "Kunne ikke koble til Tor-kontrolleren. Kjører Tor-nettleseren (tilgjengelig fra torproject.org) i bakgrunnen?", + "settings_error_socket_port": "Kan ikke koble til Tor-kontroller på {}:{}.", + "settings_error_socket_file": "Kan ikke koble til Tor-kontroller ved bruk av socket-fil {}.", + "settings_error_auth": "Tilkoblet til {}:{}, men kan ikke identitetsbekrefte. Kanskje dette ikke er en Tor-kontroller?", + "settings_error_missing_password": "Tilkoblet til Tor-kontroller, men den krever et passord for å identitetsbekrefte.", + "settings_error_unreadable_cookie_file": "Tilkoblet til Tor-kontrolleren, men passordet kan være galt, eller så har ikke brukeren din tilgang til å lese fra kakefilen.", + "settings_error_bundled_tor_not_supported": "Bruk av Tor-versjonen som kommer med OnionShare fungerer ikke i utviklermodus på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "Det tar for lang tid å koble til Tor. Kanskje du ikke er koblet til Internett, eller har du kanskje en unøyaktig systemklokke?", + "settings_error_bundled_tor_broken": "OnionShare kunne ikke koble til Tor i bakgrunnen:\n{}", + "settings_test_success": "Koblet til Tor-kontrolleren.\n\nTor-versjon: {}\nStøtter flyktige løktjenester: {}.\nStøtter klientidentifiserings", + "error_tor_protocol_error": "Feil med Tor: {}", + "error_tor_protocol_error_unknown": "Ukjent feil med Tor", + "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet", + "connecting_to_tor": "Kobler til Tor-nettverket", + "update_available": "Ny OnionShare lansert. Klikk her for å hente det.

    Du bruker {} og nyeste versjon er {}.", + "update_error_check_error": "Kunne ikke sjekke etter nye versjoner: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", + "update_error_invalid_latest_version": "Kunne ikke sjekke etter ny versjon: Kanskje du ikke er koblet til Tor, eller kanskje OnionShare-nettsiden er nede?", + "update_not_available": "Du kjører siste OnionShare.", + "gui_tor_connection_ask": "Åpne innstillingene for å ordne opp i tilkoblingen til Tor?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_ask_quit": "Avslutt", + "gui_tor_connection_error_settings": "Prøv å endre hvordan OnionShare kobler til Tor-nettverket i innstillingene.", + "gui_tor_connection_canceled": "Kunne ikke koble til Tor.\n\nForsikre deg om at du er koblet til Internett, åpne så OnionShare igjen, og sett opp dets tilkobling til Tor.", + "gui_tor_connection_lost": "Frakoblet fra Tor.", + "gui_server_started_after_timeout": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", + "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", + "share_via_onionshare": "OnionShare det", + "gui_use_legacy_v2_onions_checkbox": "Bruk foreldede adresser", + "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (foreldet)", + "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-nettleseren: ", + "gui_receive_url_description": "Alle som har denne OnionShare-adressen kan Laste opp filer til din datamaskin ved bruk av Tor-nettleseren: ", + "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", + "gui_url_label_stay_open": "Denne delingen vil ikke stoppe automatisk", + "gui_url_label_onetime": "Denne delingen vil stoppe etter første fullføring.", + "gui_url_label_onetime_and_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For å bruke engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", + "gui_status_indicator_share_stopped": "Klar til å dele", + "gui_status_indicator_share_working": "Starter…", + "gui_status_indicator_share_started": "Deler", + "gui_status_indicator_receive_stopped": "Klar til mottak", + "gui_status_indicator_receive_working": "Starter…", + "gui_status_indicator_receive_started": "Mottar", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "info_in_progress_downloads_tooltip": "{} nedlasting(er) underveis", + "info_completed_downloads_tooltip": "{} nedlasting(er) fullført", + "info_in_progress_uploads_tooltip": "{} opplasting(er) underveis", + "info_completed_uploads_tooltip": "{} nedlasting(er) fullført", + "error_cannot_create_downloads_dir": "Kunne ikke opprette mottaksmodusmappe: {}", + "error_downloads_dir_not_writable": "Mottaksmodusmappen er skrivebeskyttet: {}", + "receive_mode_downloads_dir": "Filer sendt til deg vil vises i denne mappen: {}", + "receive_mode_warning": "Advarsel: Mottaksmodus lar folk laste opp filer til din datamaskin. Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", + "gui_receive_mode_warning": "Mottaksmodus lar folk laste opp filer til din datamaskin.

    Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", + "receive_mode_upload_starting": "Opplasting av størrelse {} starter", + "receive_mode_received_file": "Mottatt: {}", + "gui_mode_share_button": "Del filer", + "gui_mode_receive_button": "Motta filer", + "gui_settings_receiving_label": "Mottaksinnstillinger", + "gui_settings_downloads_label": "Lagre filer i", + "gui_settings_downloads_button": "Utforstk", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Mottaksmodus kan stoppes av avsenderen", + "gui_settings_public_mode_checkbox": "Offentlig modus", + "systray_close_server_title": "OnionShare-tjener lukket", + "systray_close_server_message": "En bruker stengte tjeneren", + "systray_page_loaded_title": "OnionShare-side lastet", + "systray_download_page_loaded_message": "En bruker lastet inn nedlastingssiden", + "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden", + "gui_uploads": "Opplastingshistorikk", + "gui_no_uploads": "Ingen opplastinger enda.", + "gui_clear_history": "Tøm historikk", + "gui_upload_in_progress": "Opplasting startet {}", + "gui_upload_finished_range": "Lastet opp {} til {}", + "gui_upload_finished": "Lastet opp {}", + "gui_open_folder_error_nautilus": "Kan ikke åpne mappe fordi nautilus ikke er tilgjengelig. Filen er her: {}" } -- cgit v1.2.3-54-g00ecf From bc8d1d3f49af80ee897a178791d13a5471e9f868 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Fri, 28 Sep 2018 00:11:18 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 240 +++++++++++++++++++++++++++------------------------ 1 file changed, 127 insertions(+), 113 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index d414695b..d44b7734 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,115 +1,129 @@ { - "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", - "preparing_files": "Forbereder filer som skal deles.", - "give_this_url": "Giv denne URL til personen du sender filen til:", - "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", - "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", - "not_a_file": "{0:s} er ikke en gyldig fil.", - "not_a_readable_file": "{0:s} er ikke en læsbar fil.", - "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", - "other_page_loaded": "URL indlæst", - "close_on_timeout": "Lukker automatisk da timeout er nået", - "closing_automatically": "Lukker automatisk da download er færdig", - "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop", - "large_filesize": "Advarsel: Det kan tage timer at sende store filer", - "systray_menu_exit": "Afslut", - "systray_download_started_title": "OnionShare-download startet", - "systray_download_started_message": "En bruger startede download af dine filer", - "systray_download_completed_title": "OnionShare-download færdig", - "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", - "systray_download_canceled_title": "OnionShare-download annulleret", - "systray_download_canceled_message": "Brugeren annullerede downloaden", - "help_local_only": "Undlad at bruge tor: kun til udvikling", - "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", - "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder", - "help_stealth": "Opret usynlig onion-tjeneste (avanceret)", - "help_debug": "Log programfejl til stdout, og log webfejl til disk", - "help_filename": "Liste over filer eller mapper som skal deles", - "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", - "gui_drag_and_drop": "Træk og slip\nfiler her", - "gui_add": "Tilføj", - "gui_delete": "Slet", - "gui_choose_items": "Vælg", - "gui_share_start_server": "Start deling", - "gui_share_stop_server": "Stop deling", - "gui_copy_url": "Kopiér URL", - "gui_copy_hidservauth": "Kopiér HidServAuth", - "gui_downloads": "Downloads:", - "gui_canceled": "Annulleret", - "gui_copied_url": "Kopierede URL til udklipsholder", - "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", - "gui_please_wait": "Vent venligst...", - "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", - "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", - "gui_quit_warning_quit": "Afslut", - "gui_quit_warning_dont_quit": "Afslut ikke", - "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.", - "zip_progress_bar_format": "Databehandler filer: %p%", - "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", - "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_option": "Opret usynlige onion-tjenester", - "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", - "gui_settings_autoupdate_label": "Søg efter opdateringer", - "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer", - "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", - "gui_settings_autoupdate_timestamp_never": "Aldrig", - "gui_settings_autoupdate_check_button": "Søg efter opdateringer", - "gui_settings_sharing_label": "Valgmuligheder for deling", - "gui_settings_close_after_first_download_option": "Stop deling efter første download", - "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", - "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", - "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser", - "gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport", - "gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil", - "gui_settings_connection_type_test_button": "Test Tor-indstillinger", - "gui_settings_control_port_label": "Kontrolport", - "gui_settings_socket_file_label": "Sokkelfil", - "gui_settings_socks_label": "SOCKS-port", - "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", - "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", - "gui_settings_authenticate_password_option": "Adgangskode", - "gui_settings_password_label": "Adgangskode", - "gui_settings_tor_bridges": "Understøttelse af Tor-bro", - "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", - "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbygget obfs4 udskiftelige transporter (kræver obfs4proxy)", - "gui_settings_tor_bridges_custom_radio_option": "Brug brugerdefinerede broer", - "gui_settings_tor_bridges_custom_label": "Du kan få broer fra https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Ingen af broerne du leverede ser ud til at være gyldige, så de ignoreres.\nPrøv venligst igen med gyldige broer.", - "gui_settings_button_save": "Gem", - "gui_settings_button_cancel": "Annuller", - "gui_settings_button_help": "Hjælp", - "gui_settings_shutdown_timeout": "Stop delingen ved:", - "settings_saved": "Indstillinger gemt til {}", - "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", - "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.", - "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontroller på {}:{}", - "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontroller med sokkelfilen {}", - "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det en Tor-kontroller?", - "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere", - "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", - "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.", - "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", - "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", - "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}", - "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", - "connecting_to_tor": "Forbundet til Tor-netværket", - "update_available": "Der findes en OnionShare-opdatering. Klik her for at downloade den.

    Installeret version: {}
    Seneste version: {}", - "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.", - "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.", - "update_not_available": "Du kører den seneste version af OnionShare.", - "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", - "gui_tor_connection_ask_open_settings": "Åbn indstillinger", - "gui_tor_connection_ask_quit": "Afslut", - "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket i Indstillinger.", - "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", - "gui_tor_connection_lost": "Afbryder forbindelsen fra Tor.", - "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", - "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", - "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)" + "config_onion_service": ".", + "preparing_files": ".", + "give_this_url": ".", + "give_this_url_stealth": ".", + "ctrlc_to_stop": ".", + "not_a_file": ".", + "not_a_readable_file": ".", + "no_available_port": ".", + "other_page_loaded": ".", + "close_on_timeout": ".", + "closing_automatically": ".", + "timeout_download_still_running": ".", + "large_filesize": ".", + "systray_menu_exit": ".", + "systray_download_started_title": ".", + "systray_download_started_message": ".", + "systray_download_completed_title": ".", + "systray_download_completed_message": ".", + "systray_download_canceled_title": ".", + "systray_download_canceled_message": ".", + "help_local_only": ".", + "help_stay_open": ".", + "help_shutdown_timeout": ".", + "help_stealth": ".", + "help_debug": ".", + "help_filename": ".", + "help_config": ".", + "gui_drag_and_drop": ".", + "gui_add": ".", + "gui_delete": ".", + "gui_choose_items": ".", + "gui_share_start_server": ".", + "gui_share_stop_server": ".", + "gui_copy_url": ".", + "gui_copy_hidservauth": ".", + "gui_downloads": ".", + "gui_canceled": ".", + "gui_copied_url": ".", + "gui_copied_hidservauth": ".", + "gui_please_wait": ".", + "gui_download_upload_progress_complete": ".", + "gui_download_upload_progress_starting": ".", + "gui_download_upload_progress_eta": ".", + "version_string": ".", + "gui_share_quit_warning": ".", + "gui_quit_warning_quit": ".", + "gui_quit_warning_dont_quit": ".", + "error_rate_limit": ".", + "zip_progress_bar_format": ".", + "error_stealth_not_supported": ".", + "error_ephemeral_not_supported": ".", + "gui_settings_window_title": ".", + "gui_settings_stealth_option": ".", + "gui_settings_stealth_hidservauth_string": ".", + "gui_settings_autoupdate_label": ".", + "gui_settings_autoupdate_option": ".", + "gui_settings_autoupdate_timestamp": ".", + "gui_settings_autoupdate_timestamp_never": ".", + "gui_settings_autoupdate_check_button": ".", + "gui_settings_sharing_label": ".", + "gui_settings_close_after_first_download_option": ".", + "gui_settings_connection_type_label": ".", + "gui_settings_connection_type_bundled_option": ".", + "gui_settings_connection_type_automatic_option": ".", + "gui_settings_connection_type_control_port_option": ".", + "gui_settings_connection_type_socket_file_option": ".", + "gui_settings_connection_type_test_button": ".", + "gui_settings_control_port_label": ".", + "gui_settings_socket_file_label": ".", + "gui_settings_socks_label": ".", + "gui_settings_authenticate_label": ".", + "gui_settings_authenticate_no_auth_option": ".", + "gui_settings_authenticate_password_option": ".", + "gui_settings_password_label": ".", + "gui_settings_tor_bridges": ".", + "gui_settings_tor_bridges_no_bridges_radio_option": ".", + "gui_settings_tor_bridges_obfs4_radio_option": ".", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": ".", + "gui_settings_tor_bridges_custom_radio_option": ".", + "gui_settings_tor_bridges_custom_label": ".", + "gui_settings_tor_bridges_invalid": ".", + "gui_settings_button_save": ".", + "gui_settings_button_cancel": ".", + "gui_settings_button_help": ".", + "gui_settings_shutdown_timeout": ".", + "settings_saved": ".", + "settings_error_unknown": ".", + "settings_error_automatic": ".", + "settings_error_socket_port": ".", + "settings_error_socket_file": ".", + "settings_error_auth": ".", + "settings_error_missing_password": ".", + "settings_error_unreadable_cookie_file": ".", + "settings_error_bundled_tor_not_supported": ".", + "settings_error_bundled_tor_timeout": ".", + "settings_error_bundled_tor_broken": ".", + "settings_test_success": ".", + "error_tor_protocol_error": ".", + "connecting_to_tor": ".", + "update_available": ".", + "update_error_check_error": ".", + "update_error_invalid_latest_version": ".", + "update_not_available": ".", + "gui_tor_connection_ask": ".", + "gui_tor_connection_ask_open_settings": ".", + "gui_tor_connection_ask_quit": ".", + "gui_tor_connection_error_settings": ".", + "gui_tor_connection_canceled": ".", + "gui_tor_connection_lost": ".", + "gui_server_started_after_timeout": ".", + "gui_server_timeout_expired": ".", + "share_via_onionshare": ".", + "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)", + "gui_copied_url_title": "Kopierede OnionShare-adresse", + "gui_copied_hidservauth_title": "Kopierede HidServAuth", + "gui_quit_title": "Igangværende overførsel", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Brug indbyggede meek_lite (Azure) udskiftelige transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Brug indbyggede meek_lite (Azure) udskiftelige transporter (kræver obfs4proxy)", + "gui_settings_shutdown_timeout_checkbox": "Brug timer med autostop", + "gui_url_label_persistent": "Delingen stopper ikke automatisk, med mindre der er sat en timer.

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", + "gui_url_label_stay_open": "Delingen stopper ikke automatisk, med mindre der er sat en timer.", + "gui_url_label_onetime": "Delingen stopper efter den første download", + "gui_url_label_onetime_and_persistent": "Delingen stopper efter den første download

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "info_in_progress_downloads_tooltip": "{} igangværende downloads", + "info_completed_downloads_tooltip": "{} færdige downloads" } -- cgit v1.2.3-54-g00ecf From 7ce981fd8f886ce3ffe39a597b97d5d8e160e994 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Fri, 28 Sep 2018 00:34:22 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 214 +++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index d44b7734..724af134 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,117 +1,117 @@ { - "config_onion_service": ".", - "preparing_files": ".", - "give_this_url": ".", - "give_this_url_stealth": ".", - "ctrlc_to_stop": ".", - "not_a_file": ".", - "not_a_readable_file": ".", - "no_available_port": ".", - "other_page_loaded": ".", - "close_on_timeout": ".", - "closing_automatically": ".", - "timeout_download_still_running": ".", - "large_filesize": ".", - "systray_menu_exit": ".", - "systray_download_started_title": ".", - "systray_download_started_message": ".", - "systray_download_completed_title": ".", - "systray_download_completed_message": ".", - "systray_download_canceled_title": ".", - "systray_download_canceled_message": ".", - "help_local_only": ".", - "help_stay_open": ".", - "help_shutdown_timeout": ".", - "help_stealth": ".", - "help_debug": ".", - "help_filename": ".", - "help_config": ".", - "gui_drag_and_drop": ".", - "gui_add": ".", - "gui_delete": ".", - "gui_choose_items": ".", + "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", + "preparing_files": "Forbereder filer som skal deles.", + "give_this_url": "Giv adressen til personen du sender filen til:", + "give_this_url_stealth": "Giv adressen og HidServAuth-linjen til personen du sender filen til:", + "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", + "not_a_file": "{0:s} er ikke en gyldig fil.", + "not_a_readable_file": "{0:s} er ikke en læsbar fil.", + "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", + "other_page_loaded": "Adresse indlæst", + "close_on_timeout": "Stoppede fordi timeren løb ud", + "closing_automatically": "Stoppede fordi download er færdig", + "timeout_download_still_running": "Venter på at download skal blive færdig", + "large_filesize": "Advarsel: Det kan tage timer at sende store filer", + "systray_menu_exit": "Afslut", + "systray_download_started_title": "OnionShare-download startet", + "systray_download_started_message": "En bruger startede download af dine filer", + "systray_download_completed_title": "OnionShare-download færdig", + "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", + "systray_download_canceled_title": "OnionShare-download annulleret", + "systray_download_canceled_message": "Brugeren annullerede downloaden", + "help_local_only": "Undlad at bruge tor: kun til udvikling", + "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", + "help_shutdown_timeout": "Luk ned for onion-tjenesten efter N sekunder", + "help_stealth": "Opret skjult onion-tjeneste (avanceret)", + "help_debug": "Log programfejl til stdout, og log webfejl til disk", + "help_filename": "Liste over filer eller mapper som skal deles", + "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", + "gui_drag_and_drop": "Træk og slip filer og mapper her\nfor at starte deling", + "gui_add": "Tilføj", + "gui_delete": "Slet", + "gui_choose_items": "Vælg", "gui_share_start_server": ".", "gui_share_stop_server": ".", - "gui_copy_url": ".", - "gui_copy_hidservauth": ".", - "gui_downloads": ".", - "gui_canceled": ".", - "gui_copied_url": ".", - "gui_copied_hidservauth": ".", - "gui_please_wait": ".", + "gui_copy_url": "Kopiér adresse", + "gui_copy_hidservauth": "Kopiér HidServAuth", + "gui_downloads": "Downloads:", + "gui_canceled": "Annulleret", + "gui_copied_url": "OnionShare-adressen er blevet kopieret til udklipsholderen", + "gui_copied_hidservauth": "HidServAuth-linjen er blevet kopieret til udklipsholderen", + "gui_please_wait": "Starter... klik for at annullere", "gui_download_upload_progress_complete": ".", "gui_download_upload_progress_starting": ".", "gui_download_upload_progress_eta": ".", - "version_string": ".", + "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": ".", - "gui_quit_warning_quit": ".", - "gui_quit_warning_dont_quit": ".", - "error_rate_limit": ".", - "zip_progress_bar_format": ".", - "error_stealth_not_supported": ".", - "error_ephemeral_not_supported": ".", - "gui_settings_window_title": ".", - "gui_settings_stealth_option": ".", - "gui_settings_stealth_hidservauth_string": ".", - "gui_settings_autoupdate_label": ".", - "gui_settings_autoupdate_option": ".", - "gui_settings_autoupdate_timestamp": ".", - "gui_settings_autoupdate_timestamp_never": ".", - "gui_settings_autoupdate_check_button": ".", - "gui_settings_sharing_label": ".", - "gui_settings_close_after_first_download_option": ".", - "gui_settings_connection_type_label": ".", - "gui_settings_connection_type_bundled_option": ".", - "gui_settings_connection_type_automatic_option": ".", - "gui_settings_connection_type_control_port_option": ".", - "gui_settings_connection_type_socket_file_option": ".", - "gui_settings_connection_type_test_button": ".", - "gui_settings_control_port_label": ".", - "gui_settings_socket_file_label": ".", - "gui_settings_socks_label": ".", - "gui_settings_authenticate_label": ".", - "gui_settings_authenticate_no_auth_option": ".", - "gui_settings_authenticate_password_option": ".", - "gui_settings_password_label": ".", - "gui_settings_tor_bridges": ".", - "gui_settings_tor_bridges_no_bridges_radio_option": ".", - "gui_settings_tor_bridges_obfs4_radio_option": ".", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": ".", - "gui_settings_tor_bridges_custom_radio_option": ".", - "gui_settings_tor_bridges_custom_label": ".", - "gui_settings_tor_bridges_invalid": ".", - "gui_settings_button_save": ".", - "gui_settings_button_cancel": ".", - "gui_settings_button_help": ".", - "gui_settings_shutdown_timeout": ".", - "settings_saved": ".", - "settings_error_unknown": ".", - "settings_error_automatic": ".", - "settings_error_socket_port": ".", - "settings_error_socket_file": ".", - "settings_error_auth": ".", - "settings_error_missing_password": ".", - "settings_error_unreadable_cookie_file": ".", - "settings_error_bundled_tor_not_supported": ".", - "settings_error_bundled_tor_timeout": ".", - "settings_error_bundled_tor_broken": ".", - "settings_test_success": ".", - "error_tor_protocol_error": ".", - "connecting_to_tor": ".", - "update_available": ".", - "update_error_check_error": ".", - "update_error_invalid_latest_version": ".", - "update_not_available": ".", - "gui_tor_connection_ask": ".", - "gui_tor_connection_ask_open_settings": ".", - "gui_tor_connection_ask_quit": ".", - "gui_tor_connection_error_settings": ".", - "gui_tor_connection_canceled": ".", - "gui_tor_connection_lost": ".", - "gui_server_started_after_timeout": ".", - "gui_server_timeout_expired": ".", - "share_via_onionshare": ".", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)", + "gui_quit_warning_quit": "Afslut", + "gui_quit_warning_dont_quit": "Annuller", + "error_rate_limit": "En angriber forsøger måske at gætte din adresse. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye adresse.", + "zip_progress_bar_format": "Databehandler filer: %p%", + "error_stealth_not_supported": "For at oprette skjulte onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", + "gui_settings_window_title": "Indstillinger", + "gui_settings_stealth_option": "Opret skjulte onion-tjenester", + "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", + "gui_settings_autoupdate_label": "Søg efter opdateringer", + "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer", + "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", + "gui_settings_autoupdate_timestamp_never": "Aldrig", + "gui_settings_autoupdate_check_button": "Søg efter opdateringer", + "gui_settings_sharing_label": "Valgmuligheder for deling", + "gui_settings_close_after_first_download_option": "Stop deling efter første download", + "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", + "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", + "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser", + "gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport", + "gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil", + "gui_settings_connection_type_test_button": "Test Tor-indstillinger", + "gui_settings_control_port_label": "Kontrolport", + "gui_settings_socket_file_label": "Sokkelfil", + "gui_settings_socks_label": "SOCKS-port", + "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", + "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", + "gui_settings_authenticate_password_option": "Adgangskode", + "gui_settings_password_label": "Adgangskode", + "gui_settings_tor_bridges": "Understøttelse af Tor-bro", + "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", + "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbyggede obfs4 udskiftelige transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbyggede obfs4 udskiftelige transporter (kræver obfs4proxy)", + "gui_settings_tor_bridges_custom_radio_option": "Brug brugerdefinerede broer", + "gui_settings_tor_bridges_custom_label": "Du kan få broer fra https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ingen af de broer du leverede ser ud til at være gyldige.\nPrøv venligst igen med gyldige broer.", + "gui_settings_button_save": "Gem", + "gui_settings_button_cancel": "Annuller", + "gui_settings_button_help": "Hjælp", + "gui_settings_shutdown_timeout": "Stop delingen ved:", + "settings_saved": "Indstillinger gemt til {}", + "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", + "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.", + "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontroller på {}:{}.", + "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontroller med sokkelfilen {}.", + "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?", + "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.", + "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", + "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller macOS.", + "settings_error_bundled_tor_timeout": "Det tager for lang tid at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", + "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", + "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter skjulte onion-tjenester: {}", + "error_tor_protocol_error": "Fejl ved snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", + "connecting_to_tor": "Opretter forbindelse til Tor-netværket", + "update_available": "Der findes en opdatering til OnionShare. Klik her for at downloade den.

    Installeret version: {}
    Seneste version: {}", + "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.", + "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.", + "update_not_available": "Du kører den seneste version af OnionShare.", + "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", + "gui_tor_connection_ask_open_settings": "Åbn indstillinger", + "gui_tor_connection_ask_quit": "Afslut", + "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillinger.", + "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", + "gui_tor_connection_lost": "Der er ikke forbindelse til Tor.", + "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", + "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", + "share_via_onionshare": "Del via OnionShare", + "gui_save_private_key_checkbox": "Brug en vedvarende adresse\n(fravalg vil slette gemt adresse)", "gui_copied_url_title": "Kopierede OnionShare-adresse", "gui_copied_hidservauth_title": "Kopierede HidServAuth", "gui_quit_title": "Igangværende overførsel", -- cgit v1.2.3-54-g00ecf From 593d220fed9ce25b0c2321136dddff8b4f4cd0c8 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Thu, 27 Sep 2018 04:12:24 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/share/locale/no.json b/share/locale/no.json index 4b3fd97e..9d2758de 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -2,7 +2,7 @@ "give_this_url": "Gi denne adressen til mottakeren:", "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", - "gui_copied_url": "Kopierte URL-en til utklippstavlen", + "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.", "preparing_files": "Pakker sammen filer.", @@ -52,7 +52,7 @@ "gui_copied_url_title": "Kopierte OnionShare-adressen", "gui_copied_hidservauth_title": "Kopierte HidServAuth-linje", "gui_copied_hidservauth": "HidServAuth-linje kopiert til utklippstavle", - "gui_please_wait": "Starter… Klikk for å avbryte", + "gui_please_wait": "Starter… Klikk for å avbryte.", "gui_download_upload_progress_complete": "%p%, {0:s} forløpt.", "gui_download_upload_progress_starting": "{0:s}, %p% (regner ut)", "gui_download_upload_progress_eta": "{0:s}, anslått ferdigstilt: {1:s}, %p%", @@ -64,11 +64,11 @@ "gui_quit_warning_dont_quit": "Avbryt", "error_rate_limit": "Noen har prøvd adressen din for mange ganger, som kan betyr at de prøver å gjette seg fram til den, OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.", "zip_progress_bar_format": "Pakker sammen: %p%", - "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-nettleseren 6.5) og python3-stem 1.5.0", + "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-nettleseren 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", "gui_settings_window_title": "Innstillinger", "gui_settings_whats_this": "Hva er dette?", - "gui_settings_stealth_option": "Bruk klientidentifisering (foreldet)", + "gui_settings_stealth_option": "Bruk klientidentifisering (gammeldags)", "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.", "gui_settings_autoupdate_label": "Se etter ny versjon", "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig", @@ -117,7 +117,7 @@ "settings_error_bundled_tor_not_supported": "Bruk av Tor-versjonen som kommer med OnionShare fungerer ikke i utviklermodus på Windows eller macOS.", "settings_error_bundled_tor_timeout": "Det tar for lang tid å koble til Tor. Kanskje du ikke er koblet til Internett, eller har du kanskje en unøyaktig systemklokke?", "settings_error_bundled_tor_broken": "OnionShare kunne ikke koble til Tor i bakgrunnen:\n{}", - "settings_test_success": "Koblet til Tor-kontrolleren.\n\nTor-versjon: {}\nStøtter flyktige løktjenester: {}.\nStøtter klientidentifiserings", + "settings_test_success": "Koblet til Tor-kontrolleren.\n\nTor-versjon: {}.\nStøtter flyktige løktjenester: {}.\nStøtter klientidentifisering: {}.\nStøtter nestegenerasjons .onion-adresser: {}.", "error_tor_protocol_error": "Feil med Tor: {}", "error_tor_protocol_error_unknown": "Ukjent feil med Tor", "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet", @@ -135,12 +135,12 @@ "gui_server_started_after_timeout": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", "share_via_onionshare": "OnionShare det", - "gui_use_legacy_v2_onions_checkbox": "Bruk foreldede adresser", - "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (foreldet)", + "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", + "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (gammeldags)", "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-nettleseren: ", "gui_receive_url_description": "Alle som har denne OnionShare-adressen kan Laste opp filer til din datamaskin ved bruk av Tor-nettleseren: ", "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", - "gui_url_label_stay_open": "Denne delingen vil ikke stoppe automatisk", + "gui_url_label_stay_open": "Denne delingen vil ikke stoppe automatisk.", "gui_url_label_onetime": "Denne delingen vil stoppe etter første fullføring.", "gui_url_label_onetime_and_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For å bruke engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", "gui_status_indicator_share_stopped": "Klar til å dele", -- cgit v1.2.3-54-g00ecf From 502c0b6eea8355f8a986339458643fec8ddd207e Mon Sep 17 00:00:00 2001 From: emma peel Date: Thu, 4 Oct 2018 00:00:53 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 49 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 8c9945a8..1f531068 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,21 +1,50 @@ { - "preparing_files": "Preparando los archivos para compartir.", - "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", - "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", + "preparing_files": "Comprimiendo los archivos.", + "give_this_url": "Entrega esta URL al receptor:", + "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", - "other_page_loaded": "La URL está lista.", + "other_page_loaded": "La URL está lista", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", - "help_local_only": "No intentar usar Tor: sólo para desarrollo", - "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado", + "help_local_only": "No usar Tor (sólo para desarrollo)", + "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", "help_debug": "Guardar registro de errores en el disco", "help_filename": "Lista de archivos o carpetas para compartir", "gui_drag_and_drop": "Arrastre\narchivos aquí", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", - "gui_share_start_server": "Encender el Servidor", - "gui_share_stop_server": "Detener el Servidor", + "gui_share_start_server": "Comenzar a compartir", + "gui_share_stop_server": "Dejar de compartir", "gui_copy_url": "Copiar URL", - "gui_downloads": "Descargas:", - "gui_copied_url": "Se copió la URL en el portapapeles" + "gui_downloads": "Historial de descargas", + "gui_copied_url": "Dirección OnionShare copiada al portapapeles", + "config_onion_service": "Configurando el servicio cebolla en el puerto {0:d}.", + "give_this_url_stealth": "Dale esta dirección y la línea de HidServAuth a la persona a la que le estás enviando el archivo:", + "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible.", + "close_on_timeout": "Parado porque el temporizador expiró", + "timeout_download_still_running": "Esperando a que se complete la descarga", + "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas", + "help_shutdown_timeout": "Dejar de compartir después de una determinada cantidad de segundos", + "help_stealth": "Usar autorización de cliente (avanzada)", + "help_config": "Ubicación del archivo de configuración JSON personalizado (opcional)", + "gui_copied_url_title": "Dirección de OnionShare copiada", + "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles", + "gui_please_wait": "Comenzando.... Haz clic para cancelar.", + "gui_quit_title": "No tan rápido", + "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarlo, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", + "zip_progress_bar_format": "Comprimiendo: %p%", + "error_stealth_not_supported": "Para crear servicios de cebolla sigilosos, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requiere al menos Tor 0.2.7.1 y al menos python3-stem 1.4.0.", + "gui_settings_window_title": "Configuración", + "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", + "gui_settings_stealth_hidservauth_string": "Después de haber guardado su clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", + "gui_settings_autoupdate_label": "Control para versión nueva", + "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", + "gui_settings_autoupdate_check_button": "Buscar una Nueva Versión", + "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare", + "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor", + "gui_settings_connection_type_test_button": "Probar la conexión a Tor", + "gui_settings_tor_bridges": "Soporte de puentes de Tor", + "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", + "settings_saved": "Ajustes guardados en {}" } -- cgit v1.2.3-54-g00ecf From 1039053478ed0f4f84022711f64c872ee945675c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 6 Oct 2018 21:02:29 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index 724af134..1c87e443 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -30,7 +30,7 @@ "gui_add": "Tilføj", "gui_delete": "Slet", "gui_choose_items": "Vælg", - "gui_share_start_server": ".", + "gui_share_start_server": "Begynd at dele", "gui_share_stop_server": ".", "gui_copy_url": "Kopiér adresse", "gui_copy_hidservauth": "Kopiér HidServAuth", @@ -40,7 +40,7 @@ "gui_copied_hidservauth": "HidServAuth-linjen er blevet kopieret til udklipsholderen", "gui_please_wait": "Starter... klik for at annullere", "gui_download_upload_progress_complete": ".", - "gui_download_upload_progress_starting": ".", + "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", "gui_download_upload_progress_eta": ".", "version_string": "Onionshare {0:s} | https://onionshare.org/", "gui_share_quit_warning": ".", -- cgit v1.2.3-54-g00ecf From 87b67bedc4a88c218da0b3edb95decb9fc008acf Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 5 Oct 2018 20:47:48 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 1f531068..02be03f3 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -7,7 +7,7 @@ "closing_automatically": "Apagando automáticamente porque la descarga finalizó", "help_local_only": "No usar Tor (sólo para desarrollo)", "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", - "help_debug": "Guardar registro de errores en el disco", + "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", "gui_drag_and_drop": "Arrastre\narchivos aquí", "gui_add": "Añadir", @@ -20,7 +20,7 @@ "gui_copied_url": "Dirección OnionShare copiada al portapapeles", "config_onion_service": "Configurando el servicio cebolla en el puerto {0:d}.", "give_this_url_stealth": "Dale esta dirección y la línea de HidServAuth a la persona a la que le estás enviando el archivo:", - "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible.", + "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible", "close_on_timeout": "Parado porque el temporizador expiró", "timeout_download_still_running": "Esperando a que se complete la descarga", "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas", @@ -46,5 +46,78 @@ "gui_settings_connection_type_test_button": "Probar la conexión a Tor", "gui_settings_tor_bridges": "Soporte de puentes de Tor", "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", - "settings_saved": "Ajustes guardados en {}" + "settings_saved": "Ajustes guardados en {}", + "give_this_url_receive": "Dale esta dirección al remitente:", + "give_this_url_receive_stealth": "Dar esta dirección y HidServAuth al remitente:", + "not_a_readable_file": "{0:s} no es un archivo legible.", + "systray_menu_exit": "Salir", + "systray_download_started_title": "Iniciada la descarga de OnionShare", + "systray_download_started_message": "Alguien comenzó a descargar tus archivos", + "systray_download_completed_title": "Descarga de OnionShare finalizada", + "systray_download_completed_message": "Alguien ha terminado de descargar tus archivos", + "systray_download_canceled_title": "Descarga de OnionShare Cancelada", + "systray_download_canceled_message": "El usuario canceló la descarga", + "systray_upload_started_title": "Subida OnionShare Iniciada", + "systray_upload_started_message": "Un usuario comenzó a subir archivos a tu computadora", + "help_receive": "Recibir recursos compartidos en lugar de enviarlos", + "gui_share_stop_server_shutdown_timeout": "Dejar de Compartir ({}s restantes)", + "gui_share_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}", + "gui_receive_start_server": "Iniciar el modo de recepción", + "gui_receive_stop_server": "Detener el modo de recepción", + "gui_receive_stop_server_shutdown_timeout": "Detener el modo de recepción ({}s restantes)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}", + "gui_copy_hidservauth": "Copiar HidServAuth", + "gui_no_downloads": "Ninguna descarga todavía.", + "gui_canceled": "Cancelado", + "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", + "settings_error_unknown": "No se puede conectar al controlador Tor porque la configuración no tiene sentido.", + "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", + "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", + "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", + "settings_error_auth": "Conectado a {}:{}, pero no se puede autentificar. ¿Quizás este no sea un controlador Tor?", + "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero no puedo autenticarme porque la contraseña parece estar equivocada, y tu usuario no tiene permiso para leer el archivo cookie.", + "settings_error_bundled_tor_not_supported": "Bundled Tor no sepuede usar si no se usa el modo de desarrollo en Windows o macOS.", + "settings_error_bundled_tor_timeout": "Conectarse con Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado, o el reloj no está en hora?", + "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", + "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímera: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", + "error_tor_protocol_error": "Hubo un error con Tor: {}", + "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", + "error_invalid_private_key": "Este tipo de clave privada no es compatible", + "connecting_to_tor": "Conexión a la red Tor", + "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Br>Estás usando {} y el último es {}.", + "update_error_check_error": "No se han podido comprobar las nuevas versiones: El sitio web de OnionShare dice que la última versión es la irreconocible '{}'.…", + "update_error_invalid_latest_version": "No se ha podido comprobar la nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Estás ejecutando la última versión de OnionShare.", + "gui_tor_connection_ask": "Abrir la configuración para arrreglar la conexión a Tor?", + "gui_tor_connection_ask_open_settings": "sí", + "gui_tor_connection_ask_quit": "Salir", + "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configura tu conexión a Tor.", + "gui_tor_connection_lost": "Desconectado de Tor.", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor, haz una nueva porción.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", + "share_via_onionshare": "Compártelo con OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Usar direcciones de legado", + "gui_save_private_key_checkbox": "Usar una dirección persistente (legado)", + "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Tor Browser: ", + "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Tor Browser: ", + "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", + "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_status_indicator_share_stopped": "Listo para compartir", + "gui_status_indicator_share_working": "Comenzando.…", + "gui_status_indicator_share_started": "Compartir", + "gui_status_indicator_receive_stopped": "Listo para recibir", + "gui_status_indicator_receive_working": "Comenzando.…", + "gui_status_indicator_receive_started": "Recibiendo", + "gui_file_info": "{} archivos, {}", + "gui_file_info_single": "{} archivo, {}", + "info_in_progress_downloads_tooltip": "{} descarga(s) en curso", + "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", + "info_in_progress_uploads_tooltip": "{} subida(s) en curso", + "info_completed_uploads_tooltip": "{} subida(s) completada(s)", + "receive_mode_downloads_dir": "Los archivos enviados a ti aparecen en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo." } -- cgit v1.2.3-54-g00ecf From 4a0a9b96efe387cba64fe4580945bf7258c3192c Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Thu, 11 Oct 2018 05:11:22 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/share/locale/no.json b/share/locale/no.json index 9d2758de..d0b2661f 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -160,7 +160,7 @@ "receive_mode_downloads_dir": "Filer sendt til deg vil vises i denne mappen: {}", "receive_mode_warning": "Advarsel: Mottaksmodus lar folk laste opp filer til din datamaskin. Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", "gui_receive_mode_warning": "Mottaksmodus lar folk laste opp filer til din datamaskin.

    Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", - "receive_mode_upload_starting": "Opplasting av størrelse {} starter", + "receive_mode_upload_starting": "Opplasting av total størrelse {} starter", "receive_mode_received_file": "Mottatt: {}", "gui_mode_share_button": "Del filer", "gui_mode_receive_button": "Motta filer", @@ -176,9 +176,14 @@ "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden", "gui_uploads": "Opplastingshistorikk", "gui_no_uploads": "Ingen opplastinger enda.", - "gui_clear_history": "Tøm historikk", + "gui_clear_history": "Tøm alt", "gui_upload_in_progress": "Opplasting startet {}", "gui_upload_finished_range": "Lastet opp {} til {}", "gui_upload_finished": "Lastet opp {}", - "gui_open_folder_error_nautilus": "Kan ikke åpne mappe fordi nautilus ikke er tilgjengelig. Filen er her: {}" + "gui_open_folder_error_nautilus": "Kan ikke åpne mappe fordi nautilus ikke er tilgjengelig. Filen er her: {}", + "history_in_progress_tooltip": "{} underveis", + "history_completed_tooltip": "{} fullført", + "gui_download_in_progress": "Nedlasting startet {}", + "gui_settings_language_label": "Foretrukket språk", + "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg." } -- cgit v1.2.3-54-g00ecf From ed433b1d5cc4007d9cc91c7b0504245860f57b11 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Sat, 13 Oct 2018 14:44:33 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index ee206662..232e9188 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,7 +1,7 @@ { - "preparing_files": "Préparation des fichiers à partager.", - "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", - "ctrlc_to_stop": "Ctrl-C arrête le serveur", + "preparing_files": "Compression de fichiers.", + "give_this_url": "Donnez cette adresse au destinataire :", + "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", "not_a_file": "{0:s} n'est pas un fichier.", "other_page_loaded": "URL chargée", "closing_automatically": "Fermeture automatique car le téléchargement est fini", @@ -11,7 +11,7 @@ "systray_download_completed_title": "Téléchargement OnionShare Complete", "systray_download_canceled_title": "Téléchargement OnionShare Annulé", "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", - "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement", + "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", "help_debug": "Enregistrer les erreurs sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", @@ -20,15 +20,15 @@ "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", "gui_share_start_server": "Démarrer le serveur", - "gui_share_stop_server": "Arrêter le serveur", + "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier URL", "gui_copy_hidservauth": "Copier HidServAuth", "gui_downloads": "Téléchargements :", "gui_canceled": "Annulé", - "gui_copied_url": "URL copié dans le presse-papier", - "gui_please_wait": "Attendez-vous...", + "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", + "gui_please_wait": "Démarrage … Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Ne quitter pas", "gui_settings_autoupdate_timestamp_never": "Jamais", - "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet" + "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet." } -- cgit v1.2.3-54-g00ecf From f573e8c6f41fa37b4e4e592c5d53577ae0ce5bdf Mon Sep 17 00:00:00 2001 From: emma peel Date: Tue, 16 Oct 2018 19:16:06 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 02be03f3..61a1c043 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -67,7 +67,7 @@ "gui_receive_stop_server_shutdown_timeout": "Detener el modo de recepción ({}s restantes)", "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}", "gui_copy_hidservauth": "Copiar HidServAuth", - "gui_no_downloads": "Ninguna descarga todavía.", + "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", "settings_error_unknown": "No se puede conectar al controlador Tor porque la configuración no tiene sentido.", @@ -119,5 +119,29 @@ "info_in_progress_uploads_tooltip": "{} subida(s) en curso", "info_completed_uploads_tooltip": "{} subida(s) completada(s)", "receive_mode_downloads_dir": "Los archivos enviados a ti aparecen en esta carpeta: {}", - "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo." + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", + "gui_download_upload_progress_eta": "{0:s}, tiempo restante: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?", + "gui_quit_warning_quit": "Salir", + "gui_quit_warning_dont_quit": "Cancelar", + "gui_settings_whats_this": "¿Qué es esto?", + "gui_settings_autoupdate_timestamp": "Última comprobación: {}", + "gui_settings_autoupdate_timestamp_never": "Nunca", + "gui_settings_general_label": "Ajustes generales", + "gui_settings_sharing_label": "Configuración de uso compartido", + "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", + "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", + "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", + "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket", + "gui_settings_control_port_label": "Puerto de control", + "gui_settings_socket_file_label": "Archivo Socket", + "gui_settings_socks_label": "Puerto SOCKS", + "gui_settings_authenticate_label": "Configuración de autenticación Tor", + "gui_settings_authenticate_no_auth_option": "Sin autenticación, o autenticación por cookies", + "gui_settings_authenticate_password_option": "Contraseña", + "gui_settings_password_label": "Contraseña", + "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes" } -- cgit v1.2.3-54-g00ecf From 7d1835153fc43a945989450501259a95e6a8c3a3 Mon Sep 17 00:00:00 2001 From: samba Date: Tue, 16 Oct 2018 20:49:25 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 73 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index ebe2df4e..ac30f107 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,25 +1,70 @@ { - "preparing_files": "Preparazione dei files da condividere.", - "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", - "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", + "preparing_files": "Preparazione dei file da condividere.", + "give_this_url": "condividi questo URL:", + "ctrlc_to_stop": "Premi Ctrl-C per terminare", "not_a_file": "{0:s} non è un file.", - "other_page_loaded": "URL caricato", - "closing_automatically": "Chiusura automatica dopo aver finito il download", + "other_page_loaded": "URL pronto", + "closing_automatically": "download completato, chiudo", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare tor: è solo per lo sviluppo", - "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", - "help_debug": "Registra gli errori sul disco", + "help_local_only": "Non usare Tor (solo per lo sviluppatori)", + "help_stay_open": "Continua condividendo anche dopo il download", + "help_debug": "Mostra gli errori sullo schermo e salva gli errori web su file", "help_filename": "Lista dei file o cartelle da condividere", - "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", + "gui_drag_and_drop": "Sposta qui file e cartelle \nper iniziare a condividere", "gui_add": "Aggiungi", "gui_delete": "Cancella", - "gui_choose_items": "Scegli", - "gui_share_start_server": "Inizia la condivisione", + "gui_choose_items": "Seleziona", + "gui_share_start_server": "Inizia a condividere", "gui_share_stop_server": "Ferma la condivisione", - "gui_copy_url": "Copia lo URL", + "gui_copy_url": "Copia URL", "gui_downloads": "Downloads:", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", - "gui_please_wait": "Attendere prego...", - "zip_progress_bar_format": "Elaborazione files: %p%" + "gui_please_wait": "Attendere prego... fai clic per interrompere", + "zip_progress_bar_format": "Elaborazione del file: %p%", + "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", + "give_this_url_stealth": "Condividi questo indirizzo e la linea HidServAuth:", + "give_this_url_receive": "Condividi questo indirizzo col destinatario:", + "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth:", + "not_a_readable_file": "{0:s} non riesco a leggere il file.", + "no_available_port": "Non trovo nessuna porta disponibile per far partire un onion service", + "close_on_timeout": "tempo scaduto, chiudo", + "timeout_download_still_running": "download in corso, attendere", + "systray_menu_exit": "Fine", + "systray_download_started_title": "Inizio il Download con OnionShare", + "systray_download_started_message": "Una persona ha iniziato il download dei tuoi file", + "systray_download_completed_title": "Download completato", + "systray_download_completed_message": "il download dei tuoi file è stato completato", + "systray_download_canceled_title": "Download cancellato", + "systray_download_canceled_message": "la persona ha interrotto il download", + "systray_upload_started_title": "Iniziando upload", + "systray_upload_started_message": "Iniziando upload di file sul tuo computer", + "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", + "help_stealth": "Richiedi autorizzazione (avanzato)", + "help_config": "Specifica il path del file JSON personalizzato", + "gui_share_stop_server_shutdown_timeout": "Ferma la condivisione ({}s rimanenti)", + "gui_share_stop_server_shutdown_timeout_tooltip": "timer termina in {}", + "gui_receive_start_server": "Inizia modalitá ricezione", + "gui_receive_stop_server": "Termina modalitá ricezione", + "gui_receive_stop_server_shutdown_timeout": "Interrompi modalitá ricezione ({}s rimanenti)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "timer termina in {}", + "gui_copy_hidservauth": "Copia URL segreto", + "gui_no_downloads": "ancora niente", + "gui_copied_url_title": "Indirizzo OnionShare copiato", + "gui_copied_hidservauth_title": "URL segreto copiato", + "gui_copied_hidservauth": "URL segreto copiato", + "gui_download_upload_progress_complete": "%p%, {0:s} mancanti.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calcolando)", + "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org", + "gui_quit_title": "Non cosí in fretta", + "gui_share_quit_warning": "Stai condividendo dei file, vuoi davvero terminare e chiudere OnionShare?", + "gui_receive_quit_warning": "Stai ricevendo file, vuoi davvero terminare e chiudere OnionShare?", + "gui_quit_warning_quit": "Esci", + "gui_quit_warning_dont_quit": "Cancella", + "error_rate_limit": "Qualcosa ha fatto troppi tentativi a questo indirizzo, questo potrebbe esporre la sicurezza dell'indirizzo, quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL che verrá generato.", + "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", + "gui_settings_window_title": "Preferenze", + "gui_settings_whats_this": "Cos'e' questo?" } -- cgit v1.2.3-54-g00ecf From 6cc8c8236bbe95b4b32262ba55afdaf6b17cc156 Mon Sep 17 00:00:00 2001 From: emma peel Date: Wed, 17 Oct 2018 12:52:33 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index 1b2d3139..f6cb2ed2 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,6 +1,6 @@ { - "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", - "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "give_this_url": "Dar este endereço para o destinatário:", + "ctrlc_to_stop": "Pressione Ctrl+C para parar o servidor", "not_a_file": "{0:s} não é um arquivo.", "gui_copied_url": "URL foi copiado para área de transferência", "other_page_loaded": "Outra página tem sido carregada" -- cgit v1.2.3-54-g00ecf From 6ac360c42dda2b6850fbd13c873a45ae8406185e Mon Sep 17 00:00:00 2001 From: emma peel Date: Wed, 17 Oct 2018 12:45:54 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index 61a1c043..bd259106 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -143,5 +143,12 @@ "gui_settings_authenticate_no_auth_option": "Sin autenticación, o autenticación por cookies", "gui_settings_authenticate_password_option": "Contraseña", "gui_settings_password_label": "Contraseña", - "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes" + "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes", + "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes enchufables obfs4 incorporados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes enchufables obfs4 incorporados (requiere obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    br>Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados" } -- cgit v1.2.3-54-g00ecf From 52beded85453a859cc8586ae02a50b2603657e41 Mon Sep 17 00:00:00 2001 From: carlosm2 Date: Thu, 18 Oct 2018 00:40:28 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index bd259106..b434b12c 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -150,5 +150,32 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    br>Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", - "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados" + "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", + "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", + "gui_settings_button_save": "Guardar", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ayuda", + "gui_settings_shutdown_timeout_checkbox": "Usar temporizador de parada automática", + "gui_settings_shutdown_timeout": "Detener el compartir en:", + "history_in_progress_tooltip": "{} En progreso", + "history_completed_tooltip": "{} completado", + "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta del modo de recepción: {}", + "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.


    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", + "receive_mode_received_file": "Recibido: {}", + "gui_mode_share_button": "Compartir archivos", + "gui_mode_receive_button": "Recibir archivos", + "gui_settings_receiving_label": "Ajustes de recepción", + "gui_settings_downloads_label": "Guardar archivos en", + "gui_settings_downloads_button": "Examinar", + "gui_settings_public_mode_checkbox": "Modo público", + "systray_close_server_title": "Servidor OnionShare cerrado", + "systray_close_server_message": "Un usuario cerró el servidor", + "systray_page_loaded_title": "Página de OnionShare Cargada", + "systray_download_page_loaded_message": "Un usuario cargó la página de descarga", + "systray_upload_page_loaded_message": "Un usuario cargó la página de carga", + "gui_uploads": "Historial de carga", + "gui_no_uploads": "No hay subidas todavía", + "gui_clear_history": "Limpiar todo" } -- cgit v1.2.3-54-g00ecf From 60f5cebe1d17edeb1b96f671cea4454f40ba65e7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 27 Oct 2018 18:51:49 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index b434b12c..e25a9b0e 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -80,7 +80,7 @@ "settings_error_bundled_tor_not_supported": "Bundled Tor no sepuede usar si no se usa el modo de desarrollo en Windows o macOS.", "settings_error_bundled_tor_timeout": "Conectarse con Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado, o el reloj no está en hora?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímera: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", + "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímeros: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", "error_invalid_private_key": "Este tipo de clave privada no es compatible", @@ -177,5 +177,12 @@ "systray_upload_page_loaded_message": "Un usuario cargó la página de carga", "gui_uploads": "Historial de carga", "gui_no_uploads": "No hay subidas todavía", - "gui_clear_history": "Limpiar todo" + "gui_clear_history": "Limpiar todo", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "El modo de recepción puede ser detenido por el remitente", + "gui_upload_in_progress": "Subida Iniciada {}", + "gui_upload_finished": "Subido {}", + "gui_download_in_progress": "Descarga iniciada {}", + "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", + "gui_settings_language_label": "Idioma preferido", + "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto." } -- cgit v1.2.3-54-g00ecf From ee7ac1cbf982dccd80de9c6ed8ddda3bd20f4d21 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 09:31:08 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 232e9188..a61e8cc7 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -13,22 +13,27 @@ "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", - "help_debug": "Enregistrer les erreurs sur le disque", + "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs Web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", - "gui_share_start_server": "Démarrer le serveur", + "gui_share_start_server": "Commencer à partager", "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier URL", "gui_copy_hidservauth": "Copier HidServAuth", - "gui_downloads": "Téléchargements :", + "gui_downloads": "Historique De Téléchargement", "gui_canceled": "Annulé", "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", "gui_please_wait": "Démarrage … Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Ne quitter pas", "gui_settings_autoupdate_timestamp_never": "Jamais", - "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet." + "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", + "config_onion_service": "Mise en place du service de l'oignon sur le port {0:d}.", + "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", + "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", + "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", + "not_a_readable_file": "{0:s} n'est pas un fichier lisible." } -- cgit v1.2.3-54-g00ecf From c69a8d9bc9f8d928ab7c2cb42a3555bcd86ecc87 Mon Sep 17 00:00:00 2001 From: Ibrahim Derraz Date: Mon, 29 Oct 2018 09:33:09 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index a61e8cc7..c70197eb 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -28,12 +28,32 @@ "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", "gui_please_wait": "Démarrage … Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", - "gui_quit_warning_dont_quit": "Ne quitter pas", + "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", "config_onion_service": "Mise en place du service de l'oignon sur le port {0:d}.", "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", - "not_a_readable_file": "{0:s} n'est pas un fichier lisible." + "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", + "timeout_download_still_running": "En attente de la fin du téléchargement ", + "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", + "gui_copied_hidservauth_title": "HidServAuth copié ", + "gui_settings_window_title": "Paramètres ", + "gui_settings_autoupdate_timestamp": "Dernière vérification: {}", + "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement ", + "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", + "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle ", + "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", + "gui_settings_socket_file_label": "Fichier socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_no_auth_option": "Pas d'authentification ou authentification par cookie", + "gui_settings_authenticate_password_option": "Mot de passe", + "gui_settings_password_label": "Mot de passe", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de bridge", + "gui_settings_button_save": "Sauvegarder", + "gui_settings_button_cancel": "Annuler", + "gui_settings_button_help": "Aide", + "gui_settings_shutdown_timeout": "Arrêter le partage à:", + "connecting_to_tor": "Connexion au réseau Tor" } -- cgit v1.2.3-54-g00ecf From 53f463a582e4e8553324d73e1aea1ccc4b9a5640 Mon Sep 17 00:00:00 2001 From: la corneja Date: Mon, 29 Oct 2018 09:51:02 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 1d0436a0..8006ef09 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,7 +1,7 @@ { "preparing_files": "Dateien werden vorbereitet.", "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", - "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", + "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", "other_page_loaded": "URL geladen", "closing_automatically": "Halte automatisch an, da der Download beendet wurde", @@ -19,5 +19,13 @@ "gui_copy_url": "URL kopieren", "gui_downloads": "Downloads:", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", - "gui_please_wait": "Bitte warten..." + "gui_please_wait": "Bitte warten...", + "timeout_download_still_running": "Warte auf Beendigung des Downloads.", + "systray_menu_exit": "Beenden", + "gui_settings_authenticate_password_option": "Passwort", + "gui_settings_password_label": "Passwort", + "gui_settings_button_save": "Speichern", + "gui_settings_button_cancel": "Abbrechen", + "gui_settings_button_help": "Hilfe", + "gui_settings_shutdown_timeout": "Stoppe den Server bei:" } -- cgit v1.2.3-54-g00ecf From 8030d28d5f69b04a9554463e5f2adbb693e20f20 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 11:32:35 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/de.json b/share/locale/de.json index 8006ef09..09784582 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -20,7 +20,7 @@ "gui_downloads": "Downloads:", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", "gui_please_wait": "Bitte warten...", - "timeout_download_still_running": "Warte auf Beendigung des Downloads.", + "timeout_download_still_running": "Warte auf Beendigung des Downloads", "systray_menu_exit": "Beenden", "gui_settings_authenticate_password_option": "Passwort", "gui_settings_password_label": "Passwort", -- cgit v1.2.3-54-g00ecf From ec0301c73b862a38848c59ae0fdee160322694b9 Mon Sep 17 00:00:00 2001 From: la corneja Date: Mon, 29 Oct 2018 11:33:01 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 09784582..f2e655df 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -4,20 +4,20 @@ "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", "other_page_loaded": "URL geladen", - "closing_automatically": "Halte automatisch an, da der Download beendet wurde", + "closing_automatically": "Gestoppt, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Fehler auf Festplatte schreiben", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", - "gui_drag_and_drop": "Drag & drop\nDateien hier", + "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", "gui_add": "Hinzufügen", "gui_delete": "Löschen", "gui_choose_items": "Auswählen", "gui_share_start_server": "Server starten", "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", - "gui_downloads": "Downloads:", + "gui_downloads": "Bisherige Downloads:", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", "gui_please_wait": "Bitte warten...", "timeout_download_still_running": "Warte auf Beendigung des Downloads", @@ -27,5 +27,21 @@ "gui_settings_button_save": "Speichern", "gui_settings_button_cancel": "Abbrechen", "gui_settings_button_help": "Hilfe", - "gui_settings_shutdown_timeout": "Stoppe den Server bei:" + "gui_settings_shutdown_timeout": "Stoppe den Server bei:", + "systray_download_started_title": "OnionShareDownload begonnen", + "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", + "systray_download_completed_title": "OnionShareDownload beendet", + "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", + "systray_download_canceled_title": "OnionShareDownload abgebrochen", + "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen", + "gui_copy_hidservauth": "HidServAuth kopieren", + "gui_canceled": "Abgebrochen", + "gui_copied_hidservauth_title": "HidServAuth kopiert", + "gui_quit_warning_quit": "Beenden", + "gui_quit_warning_dont_quit": "Abbrechen", + "gui_settings_window_title": "Eintellungen", + "gui_settings_autoupdate_timestamp": "Letzte Überprüfung: {}", + "gui_settings_autoupdate_timestamp_never": "Niemals", + "gui_settings_close_after_first_download_option": "Server nach dem ersten Download stoppen", + "gui_settings_connection_type_label": "Wie soll sich OnionShare mit Tor verbinden?" } -- cgit v1.2.3-54-g00ecf From 4a89cee1c9c36b924b308130d54b67a3138a1bb3 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 12:09:10 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index 1c87e443..94d2b258 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -31,7 +31,7 @@ "gui_delete": "Slet", "gui_choose_items": "Vælg", "gui_share_start_server": "Begynd at dele", - "gui_share_stop_server": ".", + "gui_share_stop_server": "Stop deling", "gui_copy_url": "Kopiér adresse", "gui_copy_hidservauth": "Kopiér HidServAuth", "gui_downloads": "Downloads:", @@ -41,9 +41,9 @@ "gui_please_wait": "Starter... klik for at annullere", "gui_download_upload_progress_complete": ".", "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", - "gui_download_upload_progress_eta": ".", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": ".", + "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Annuller", "error_rate_limit": "En angriber forsøger måske at gætte din adresse. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye adresse.", @@ -125,5 +125,14 @@ "gui_file_info": "{} filer, {}", "gui_file_info_single": "{} fil, {}", "info_in_progress_downloads_tooltip": "{} igangværende downloads", - "info_completed_downloads_tooltip": "{} færdige downloads" + "info_completed_downloads_tooltip": "{} færdige downloads", + "give_this_url_receive": "Give denne adresse til afsenderen:", + "give_this_url_receive_stealth": "Giv adressen og HidServAuth-linjen til personen du sender filen til:", + "systray_upload_started_title": "OnionShare Upload Startet", + "systray_upload_started_message": "En bruger begyndte at uploade filer til din computer", + "help_receive": "Modtager aktier i stedet for at sende dem", + "gui_share_stop_server_shutdown_timeout": "Stop deling ({}s tilbage)", + "gui_receive_quit_warning": "Du er i færd med at modtage filer. Er du sikker på du ønsker at stoppe med at OnionShare?", + "gui_settings_whats_this": "Hvad er dette?", + "gui_settings_general_label": "Generel opsætning" } -- cgit v1.2.3-54-g00ecf From a474e0b2ec09a4eec31fafacb93ac15fb9f87bb0 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 21:30:42 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index abd14753..6bb1528a 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -3,7 +3,7 @@ "preparing_files": "Bestanden om te delen aan het voorbereiden.", "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", - "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", + "ctrlc_to_stop": "Druk Ctrl+C om de server te stoppen", "not_a_file": "{0:s} is geen bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", -- cgit v1.2.3-54-g00ecf From bf076023110a3863f8f284b60d4133b4613bef81 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 09:35:25 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index c70197eb..fee26e7d 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -36,14 +36,14 @@ "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", - "timeout_download_still_running": "En attente de la fin du téléchargement ", + "timeout_download_still_running": "En attente de la fin du téléchargement", "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", - "gui_copied_hidservauth_title": "HidServAuth copié ", - "gui_settings_window_title": "Paramètres ", + "gui_copied_hidservauth_title": "HidServAuth copié", + "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification: {}", - "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement ", + "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", - "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle ", + "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", "gui_settings_socket_file_label": "Fichier socket", "gui_settings_socks_label": "Port SOCKS", @@ -54,6 +54,7 @@ "gui_settings_button_save": "Sauvegarder", "gui_settings_button_cancel": "Annuler", "gui_settings_button_help": "Aide", - "gui_settings_shutdown_timeout": "Arrêter le partage à:", - "connecting_to_tor": "Connexion au réseau Tor" + "gui_settings_shutdown_timeout": "Arrêter le partage à :", + "connecting_to_tor": "Connexion au réseau Tor", + "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)" } -- cgit v1.2.3-54-g00ecf From 7477b7d0d9a6bf7c88c653874bccc0448a2b9941 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 11:36:03 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index f2e655df..9de29c24 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -17,7 +17,7 @@ "gui_share_start_server": "Server starten", "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", - "gui_downloads": "Bisherige Downloads:", + "gui_downloads": "Bisherige Downloads", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", "gui_please_wait": "Bitte warten...", "timeout_download_still_running": "Warte auf Beendigung des Downloads", @@ -30,7 +30,7 @@ "gui_settings_shutdown_timeout": "Stoppe den Server bei:", "systray_download_started_title": "OnionShareDownload begonnen", "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", - "systray_download_completed_title": "OnionShareDownload beendet", + "systray_download_completed_title": "OnionShare Download beendet", "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", "systray_download_canceled_title": "OnionShareDownload abgebrochen", "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen", -- cgit v1.2.3-54-g00ecf From f12b02ce5c6c3674ada413fb2ac30738a1ff6c21 Mon Sep 17 00:00:00 2001 From: la corneja Date: Mon, 29 Oct 2018 11:36:58 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 9de29c24..a6abb43c 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -6,8 +6,8 @@ "other_page_loaded": "URL geladen", "closing_automatically": "Gestoppt, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", - "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", - "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", + "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", + "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Fehler auf Festplatte schreiben", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", @@ -43,5 +43,37 @@ "gui_settings_autoupdate_timestamp": "Letzte Überprüfung: {}", "gui_settings_autoupdate_timestamp_never": "Niemals", "gui_settings_close_after_first_download_option": "Server nach dem ersten Download stoppen", - "gui_settings_connection_type_label": "Wie soll sich OnionShare mit Tor verbinden?" + "gui_settings_connection_type_label": "Wie soll sich OnionShare mit Tor verbinden?", + "config_onion_service": "Richte den Onionservice auf Port {0:d} ein.", + "give_this_url_stealth": "Gib dem Empfänger diese URL und die HidServAuth-Zeile:", + "give_this_url_receive": "Gib diese URL dem Sender:", + "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", + "not_a_readable_file": "{0:s} kann nicht gelesen werden.", + "no_available_port": "Konnte keinen freien Port finden, um den Onionservice zu starten", + "close_on_timeout": "Wegen Zeitablaufs gestoppt", + "systray_upload_started_title": "OnionShare Upload gestartet", + "systray_upload_started_message": "Ein Nutzer hat begonnen Dateien auf deinen Computer zu laden", + "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", + "help_receive": "Empfange Dateien anstatt sie zu senden", + "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", + "gui_settings_connection_type_control_port_option": "Verbinde über den control port", + "gui_settings_connection_type_socket_file_option": "Verbinde über ein socket file", + "gui_settings_control_port_label": "Control port", + "gui_settings_socket_file_label": "Socket file", + "gui_settings_socks_label": "SOCKS Port", + "gui_settings_authenticate_no_auth_option": "Keine Authentifizierung, oder Authentifizierung per cookie", + "gui_settings_tor_bridges_no_bridges_radio_option": "Benutze keine bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Benutze eingebaute obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Benutze eingebaute obfs4 pluggable transports (benötigt obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Benutze eingebaute meek_lite (Amazon) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Benutze eingebaute meek_lite (Azure) pluggable transports (benötigt obfs4proxy)", + "gui_settings_tor_bridges_custom_radio_option": "Benutze benutzerdefinierte bridges", + "gui_settings_tor_bridges_custom_label": "Bridges findest du unter https://bridges.torproject.org", + "gui_settings_shutdown_timeout_checkbox": "Stoppe nach einer bestimmten Zeit", + "settings_error_auth": "Mit {}:{} verbinden aber nicht authentifiziert. Eventuell handelt es sich nicht um einen Tor controller?", + "settings_error_missing_password": "Mit dem Tor controller verbunden, aber er benötigt ein Passwort zur Authentifizierung.", + "connecting_to_tor": "Verbinde mit dem Tornetzwerk", + "gui_tor_connection_ask_quit": "Beenden", + "gui_tor_connection_lost": "Verbindung zu Tor getrennt." } -- cgit v1.2.3-54-g00ecf From 19a194faa45f1a9a21311c09bca192472f7bf07f Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 09:47:00 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index ac30f107..a40a813e 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,5 +1,5 @@ { - "preparing_files": "Preparazione dei file da condividere.", + "preparing_files": "Comprimere i files.", "give_this_url": "condividi questo URL:", "ctrlc_to_stop": "Premi Ctrl-C per terminare", "not_a_file": "{0:s} non è un file.", @@ -20,13 +20,13 @@ "gui_downloads": "Downloads:", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", - "gui_please_wait": "Attendere prego... fai clic per interrompere", + "gui_please_wait": "Attendere prego... fai clic per interrompere.", "zip_progress_bar_format": "Elaborazione del file: %p%", "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", - "give_this_url_stealth": "Condividi questo indirizzo e la linea HidServAuth:", - "give_this_url_receive": "Condividi questo indirizzo col destinatario:", + "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al recipiente:", + "give_this_url_receive": "Dai questo indirizzo al mittente:", "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth:", - "not_a_readable_file": "{0:s} non riesco a leggere il file.", + "not_a_readable_file": "{0:s} non e' un file leggibile.", "no_available_port": "Non trovo nessuna porta disponibile per far partire un onion service", "close_on_timeout": "tempo scaduto, chiudo", "timeout_download_still_running": "download in corso, attendere", -- cgit v1.2.3-54-g00ecf From 3881bc613da56976f086f1d82b44755735448b0d Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 29 Oct 2018 19:43:47 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index e25a9b0e..9d1669d3 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -184,5 +184,6 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto." + "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", + "gui_upload_finished_range": "Cargado {} a {}" } -- cgit v1.2.3-54-g00ecf From 82b6274ccf95cdaeba9518d02675646129f3a9e3 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Mon, 5 Nov 2018 03:10:12 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index 94d2b258..cf5bdbea 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,25 +1,25 @@ { - "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", - "preparing_files": "Forbereder filer som skal deles.", - "give_this_url": "Giv adressen til personen du sender filen til:", - "give_this_url_stealth": "Giv adressen og HidServAuth-linjen til personen du sender filen til:", - "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", + "config_onion_service": "Opsætter onion-tjeneste på port {0:d}.", + "preparing_files": "Komprimerer filer.", + "give_this_url": "Giv adressen til modtageren:", + "give_this_url_stealth": "Giv adressen og HidServAuth-linjen til modtageren:", + "ctrlc_to_stop": "Tryk på Ctrl+C for at stoppe serveren", "not_a_file": "{0:s} er ikke en gyldig fil.", "not_a_readable_file": "{0:s} er ikke en læsbar fil.", - "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", + "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten", "other_page_loaded": "Adresse indlæst", "close_on_timeout": "Stoppede fordi timeren løb ud", "closing_automatically": "Stoppede fordi download er færdig", "timeout_download_still_running": "Venter på at download skal blive færdig", - "large_filesize": "Advarsel: Det kan tage timer at sende store filer", + "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling", "systray_menu_exit": "Afslut", - "systray_download_started_title": "OnionShare-download startet", - "systray_download_started_message": "En bruger startede download af dine filer", + "systray_download_started_title": "OnionShare-download begyndte", + "systray_download_started_message": "En bruger begyndte download af dine filer", "systray_download_completed_title": "OnionShare-download færdig", "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", "systray_download_canceled_title": "OnionShare-download annulleret", "systray_download_canceled_message": "Brugeren annullerede downloaden", - "help_local_only": "Undlad at bruge tor: kun til udvikling", + "help_local_only": "Brug ikke Tor (kun til udvikling)", "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", "help_shutdown_timeout": "Luk ned for onion-tjenesten efter N sekunder", "help_stealth": "Opret skjult onion-tjeneste (avanceret)", @@ -126,13 +126,15 @@ "gui_file_info_single": "{} fil, {}", "info_in_progress_downloads_tooltip": "{} igangværende downloads", "info_completed_downloads_tooltip": "{} færdige downloads", - "give_this_url_receive": "Give denne adresse til afsenderen:", - "give_this_url_receive_stealth": "Giv adressen og HidServAuth-linjen til personen du sender filen til:", - "systray_upload_started_title": "OnionShare Upload Startet", + "give_this_url_receive": "Giv adressen til afsenderen:", + "give_this_url_receive_stealth": "Giv adressen og HidServAuth til afsenderen:", + "systray_upload_started_title": "OnionShare-upload begyndte", "systray_upload_started_message": "En bruger begyndte at uploade filer til din computer", "help_receive": "Modtager aktier i stedet for at sende dem", "gui_share_stop_server_shutdown_timeout": "Stop deling ({}s tilbage)", "gui_receive_quit_warning": "Du er i færd med at modtage filer. Er du sikker på du ønsker at stoppe med at OnionShare?", "gui_settings_whats_this": "Hvad er dette?", - "gui_settings_general_label": "Generel opsætning" + "gui_settings_general_label": "Generel opsætning", + "gui_upload_in_progress": "Upload begyndte {}", + "gui_download_in_progress": "Download begyndte {}" } -- cgit v1.2.3-54-g00ecf From 9be2cc6a007ec805b272962d2541890df3808f04 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:14:09 +0000 Subject: Added translation using Weblate (Catalan) --- share/locale/ca.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ca.json diff --git a/share/locale/ca.json b/share/locale/ca.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/ca.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 5ece094a8ec6bd8b03ed76acfb78e40bc8ade120 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:14:21 +0000 Subject: Added translation using Weblate (Georgian) --- share/locale/ka.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ka.json diff --git a/share/locale/ka.json b/share/locale/ka.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/ka.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 29c38cc58c6a4b0dc3e3379a9f8f430fb267caee Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:14:30 +0000 Subject: Added translation using Weblate (Greek) --- share/locale/el.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/el.json diff --git a/share/locale/el.json b/share/locale/el.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/el.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From b2af41d2607a2d7517c59bc6f582d9188e399359 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:14:41 +0000 Subject: Added translation using Weblate (Hungarian) --- share/locale/hu.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/hu.json diff --git a/share/locale/hu.json b/share/locale/hu.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/hu.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From ec40e44328b40724689bb9afdec7437fd7e3204f Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:14:51 +0000 Subject: Added translation using Weblate (Icelandic) --- share/locale/is.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/is.json diff --git a/share/locale/is.json b/share/locale/is.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/is.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 55446aeb41596f9103217b8c6e61028c2f9dc29a Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:15:01 +0000 Subject: Added translation using Weblate (Irish) --- share/locale/ga.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ga.json diff --git a/share/locale/ga.json b/share/locale/ga.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/ga.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 2ce02e7b73c0cf4bbed51e8d4263457dcde567de Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:15:18 +0000 Subject: Added translation using Weblate (Persian) --- share/locale/fa.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/fa.json diff --git a/share/locale/fa.json b/share/locale/fa.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/fa.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 93e965683d83d5703ab891b87ad5a9da798769f8 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:15:29 +0000 Subject: Added translation using Weblate (Punjabi) --- share/locale/pa.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/pa.json diff --git a/share/locale/pa.json b/share/locale/pa.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/pa.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 4b4293a4f539369419e0ae2ff1ece94d976d4c8c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 07:15:54 +0000 Subject: Added translation using Weblate (Romanian) --- share/locale/ro.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ro.json diff --git a/share/locale/ro.json b/share/locale/ro.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/ro.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 5dc8eda247f4015a46c661d944eee5b2ea3d7b36 Mon Sep 17 00:00:00 2001 From: xin Date: Sun, 11 Nov 2018 09:06:23 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index fee26e7d..bba77f01 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -2,14 +2,14 @@ "preparing_files": "Compression de fichiers.", "give_this_url": "Donnez cette adresse au destinataire :", "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", - "not_a_file": "{0:s} n'est pas un fichier.", - "other_page_loaded": "URL chargée", - "closing_automatically": "Fermeture automatique car le téléchargement est fini", + "not_a_file": "{0:s} n'est pas un fichier valide.", + "other_page_loaded": "Adresse chargée", + "closing_automatically": "Arrêt automatique car le téléchargement est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare Démarré", "systray_download_started_message": "Un utilisateur télécharge vos fichiers", - "systray_download_completed_title": "Téléchargement OnionShare Complete", - "systray_download_canceled_title": "Téléchargement OnionShare Annulé", + "systray_download_completed_title": "Téléchargement OnionShare terminé", + "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", @@ -21,9 +21,9 @@ "gui_choose_items": "Sélectionnez", "gui_share_start_server": "Commencer à partager", "gui_share_stop_server": "Arrêter le partage", - "gui_copy_url": "Copier URL", + "gui_copy_url": "Copier l'adresse", "gui_copy_hidservauth": "Copier HidServAuth", - "gui_downloads": "Historique De Téléchargement", + "gui_downloads": "Historique de téléchargement", "gui_canceled": "Annulé", "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", "gui_please_wait": "Démarrage … Cliquez pour annuler.", @@ -40,7 +40,7 @@ "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", - "gui_settings_autoupdate_timestamp": "Dernière vérification: {}", + "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", @@ -56,5 +56,10 @@ "gui_settings_button_help": "Aide", "gui_settings_shutdown_timeout": "Arrêter le partage à :", "connecting_to_tor": "Connexion au réseau Tor", - "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)" + "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)", + "large_filesize": "Avertissement : envoyer un gros fichier peut prendre plusieurs heures", + "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "zip_progress_bar_format": "Compression : %p%", + "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0." } -- cgit v1.2.3-54-g00ecf From 13b02934856167ce3bc97875b51fe352ad5dcf6a Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 11 Nov 2018 09:22:23 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index bba77f01..09bc9681 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -61,5 +61,6 @@ "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p%", - "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0." + "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0.", + "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes" } -- cgit v1.2.3-54-g00ecf From 34f6ecc63432310126208d86252bd72d13d13747 Mon Sep 17 00:00:00 2001 From: communia Date: Sun, 11 Nov 2018 13:21:41 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 85 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index f6cb2ed2..1fcb2067 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,7 +1,82 @@ { - "give_this_url": "Dar este endereço para o destinatário:", - "ctrlc_to_stop": "Pressione Ctrl+C para parar o servidor", - "not_a_file": "{0:s} não é um arquivo.", - "gui_copied_url": "URL foi copiado para área de transferência", - "other_page_loaded": "Outra página tem sido carregada" + "give_this_url": "Dar este endereço ao destinatário:", + "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", + "not_a_file": "{0:s} não é um arquivo válido.", + "gui_copied_url": "URL foi copiado na área de transferência", + "other_page_loaded": "Endereço carregado", + "config_onion_service": "Configurando o serviço onion na entrada {0:d}", + "preparing_files": "Comprimindo arquivos.", + "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", + "give_this_url_receive": "Dar este endereço ao remetente:", + "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", + "not_a_readable_file": "{0:s} não é um arquivo legível.", + "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", + "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", + "closing_automatically": "Interrompido porque o download terminou", + "timeout_download_still_running": "Esperando que o download termine", + "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", + "systray_menu_exit": "Sair", + "systray_download_started_title": "O download de OnionShare começou", + "systray_download_started_message": "Alguém começou fazer o download dos seus arquivos", + "systray_download_completed_title": "O download de OnionShare terminou", + "systray_download_completed_message": "Essa pessoa terminou de fazer o download dos seus arquivos", + "systray_download_canceled_title": "O download de OnionShare foi cancelado", + "systray_download_canceled_message": "Essa pessoa cancelou o download", + "systray_upload_started_title": "OnionShare começou a carregar", + "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", + "help_local_only": "Não use Tor (unicamente para programação)", + "help_stay_open": "Continuar a compartilhar depois do primeiro download", + "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", + "help_stealth": "Usar autorização de cliente (avançado)", + "help_receive": "Receber compartilhamentos ao invés de enviá-los", + "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco", + "help_filename": "Lista de arquivos ou pastas a compartilhar", + "help_config": "Personalizar a configuração JSON de localização de arquivos (opcional)", + "gui_drag_and_drop": "Arrastar arquivos e pastas\npara começar a compartilhá-los", + "gui_add": "Adicionar", + "gui_delete": "Apagar", + "gui_choose_items": "Escolher", + "gui_share_start_server": "Começar a compartilhar", + "gui_share_stop_server": "Parar de compartilhar", + "gui_share_stop_server_shutdown_timeout": "Parar de compartilhar ({}segundos para terminar)", + "gui_share_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às", + "gui_receive_start_server": "Modo Começar a Receber", + "gui_receive_stop_server": "Modo Parar de Receber", + "gui_receive_stop_server_shutdown_timeout": "Modo Parar de Receber ({}segundos para terminar)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às {}", + "gui_copy_url": "Copiar endereço", + "gui_copy_hidservauth": "Copiar HidServAuth", + "gui_downloads": "Histórico de download", + "gui_no_downloads": "Nenhum download por enquanto", + "gui_canceled": "Cancelado", + "gui_copied_url_title": "Endereço OnionShare copiado", + "gui_copied_hidservauth_title": "HidServAuth copiado", + "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência", + "gui_please_wait": "Começando...Clique para cancelar.", + "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Mais devagar", + "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?", + "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", + "error_rate_limit": "Alguém tentou por várias vezes entrar no seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "zip_progress_bar_format": "Comprimindo: %p%", + "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", + "gui_settings_window_title": "Configurações", + "gui_settings_whats_this": "O que é isso?", + "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", + "gui_settings_autoupdate_label": "Procurar a nova versão", + "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", + "gui_settings_autoupdate_timestamp": "Última atualização: {}", + "gui_settings_autoupdate_timestamp_never": "Nunca", + "gui_settings_autoupdate_check_button": "Procurar a nova versão", + "gui_settings_general_label": "Configurações gerais", + "gui_settings_sharing_label": "Compartilhando configurações", + "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", + "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", + "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare" } -- cgit v1.2.3-54-g00ecf From 1a97f3178a45f044b226da9cb62eb7a4e9445824 Mon Sep 17 00:00:00 2001 From: communia Date: Mon, 12 Nov 2018 09:12:42 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index 1fcb2067..d5e9211d 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -78,5 +78,37 @@ "gui_settings_sharing_label": "Compartilhando configurações", "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", - "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare" + "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", + "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", + "gui_settings_connection_type_control_port_option": "Conectar usando entrada de controle", + "gui_settings_socks_label": "Entrada SOCKS", + "gui_settings_authenticate_label": "Configurações de autenticação do Tor", + "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", + "gui_settings_authenticate_password_option": "Senha", + "gui_settings_password_label": "Senha", + "gui_settings_tor_bridges": "Ajuda para pontes Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", + "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", + "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas", + "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.", + "gui_settings_button_save": "Salvar", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", + "gui_settings_shutdown_timeout_checkbox": "Usar cronômetro para encerrar automaticamente", + "gui_settings_shutdown_timeout": "Encerrar o compartilhamento às:", + "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", + "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor", + "error_invalid_private_key": "Este tipo de chave privada não possui suporte", + "connecting_to_tor": "Conectando à rede Tor", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", + "gui_status_indicator_share_stopped": "Pronto para compartilhar", + "gui_status_indicator_share_working": "Começando…", + "gui_status_indicator_share_started": "Compartilhando", + "gui_status_indicator_receive_stopped": "Pronto para receber", + "gui_status_indicator_receive_working": "Começando…", + "gui_status_indicator_receive_started": "Recebendo", + "gui_file_info": "{} arquivos, {}", + "gui_file_info_single": "{} arquivo, {}" } -- cgit v1.2.3-54-g00ecf From 32a848e8fca97110d8bf97a742cad41700bc904d Mon Sep 17 00:00:00 2001 From: communia Date: Tue, 13 Nov 2018 22:58:16 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index d5e9211d..b11fec5c 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -110,5 +110,12 @@ "gui_status_indicator_receive_working": "Começando…", "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", - "gui_file_info_single": "{} arquivo, {}" + "gui_file_info_single": "{} arquivo, {}", + "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a senha esteja incorreta, ou o seu usuário não possui autorização para ler o arquivo de cookie.", + "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", + "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", + "error_tor_protocol_error": "Houve um erro com Tor: {}", + "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", + "update_not_available": "Você está rodando a última versão de OnionShare.", + "gui_tor_connection_lost": "Desconectado do Tor." } -- cgit v1.2.3-54-g00ecf From af5f0fb61bb97f276137d2f67323c3bb94ca98d5 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Thu, 15 Nov 2018 18:17:39 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 89 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index cf5bdbea..90233582 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -8,7 +8,7 @@ "not_a_readable_file": "{0:s} er ikke en læsbar fil.", "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten", "other_page_loaded": "Adresse indlæst", - "close_on_timeout": "Stoppede fordi timeren løb ud", + "close_on_timeout": "Stoppede fordi timer med autostop løb ud", "closing_automatically": "Stoppede fordi download er færdig", "timeout_download_still_running": "Venter på at download skal blive færdig", "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling", @@ -20,7 +20,7 @@ "systray_download_canceled_title": "OnionShare-download annulleret", "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Brug ikke Tor (kun til udvikling)", - "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", + "help_stay_open": "Bliv ved med at dele efter første download", "help_shutdown_timeout": "Luk ned for onion-tjenesten efter N sekunder", "help_stealth": "Opret skjult onion-tjeneste (avanceret)", "help_debug": "Log programfejl til stdout, og log webfejl til disk", @@ -34,42 +34,42 @@ "gui_share_stop_server": "Stop deling", "gui_copy_url": "Kopiér adresse", "gui_copy_hidservauth": "Kopiér HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Downloadhistorik", "gui_canceled": "Annulleret", - "gui_copied_url": "OnionShare-adressen er blevet kopieret til udklipsholderen", - "gui_copied_hidservauth": "HidServAuth-linjen er blevet kopieret til udklipsholderen", - "gui_please_wait": "Starter... klik for at annullere", + "gui_copied_url": "OnionShare-adressen blev kopieret til udklipsholderen", + "gui_copied_hidservauth": "HidServAuth-linjen blev kopieret til udklipsholderen", + "gui_please_wait": "Starter... klik for at annullere.", "gui_download_upload_progress_complete": ".", "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Annuller", "error_rate_limit": "En angriber forsøger måske at gætte din adresse. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye adresse.", "zip_progress_bar_format": "Databehandler filer: %p%", "error_stealth_not_supported": "For at oprette skjulte onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", + "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", "gui_settings_stealth_option": "Opret skjulte onion-tjenester", "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", - "gui_settings_autoupdate_label": "Søg efter opdateringer", - "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer", + "gui_settings_autoupdate_label": "Søg efter ny version", + "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", "gui_settings_autoupdate_timestamp_never": "Aldrig", - "gui_settings_autoupdate_check_button": "Søg efter opdateringer", - "gui_settings_sharing_label": "Valgmuligheder for deling", + "gui_settings_autoupdate_check_button": "Søg efter ny version", + "gui_settings_sharing_label": "Delingsindstillinger", "gui_settings_close_after_first_download_option": "Stop deling efter første download", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", - "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser", + "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport", "gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil", - "gui_settings_connection_type_test_button": "Test Tor-indstillinger", + "gui_settings_connection_type_test_button": "Test forbindelsen til Tor", "gui_settings_control_port_label": "Kontrolport", "gui_settings_socket_file_label": "Sokkelfil", "gui_settings_socks_label": "SOCKS-port", - "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", + "gui_settings_authenticate_label": "Indstillinger for Tor-autentifikation", "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", "gui_settings_authenticate_password_option": "Adgangskode", "gui_settings_password_label": "Adgangskode", @@ -85,10 +85,10 @@ "gui_settings_button_help": "Hjælp", "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", - "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", + "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da dine indstillingerne ikke giver mening.", "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.", - "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontroller på {}:{}.", - "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontroller med sokkelfilen {}.", + "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontrolleren på {}:{}.", + "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontrolleren med sokkelfilen {}.", "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?", "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.", "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", @@ -96,20 +96,20 @@ "settings_error_bundled_tor_timeout": "Det tager for lang tid at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter skjulte onion-tjenester: {}", - "error_tor_protocol_error": "Fejl ved snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", + "error_tor_protocol_error": "Der opstod en fejl med Tor: {}", "connecting_to_tor": "Opretter forbindelse til Tor-netværket", "update_available": "Der findes en opdatering til OnionShare. Klik her for at downloade den.

    Installeret version: {}
    Seneste version: {}", "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.", "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.", - "update_not_available": "Du kører den seneste version af OnionShare.", + "update_not_available": "Du kører den seneste OnionShare.", "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", - "gui_tor_connection_ask_open_settings": "Åbn indstillinger", + "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afslut", "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillinger.", "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", - "gui_tor_connection_lost": "Der er ikke forbindelse til Tor.", - "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", - "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", + "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.", + "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", + "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", "share_via_onionshare": "Del via OnionShare", "gui_save_private_key_checkbox": "Brug en vedvarende adresse\n(fravalg vil slette gemt adresse)", "gui_copied_url_title": "Kopierede OnionShare-adresse", @@ -119,8 +119,8 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Brug indbyggede meek_lite (Azure) udskiftelige transporter (kræver obfs4proxy)", "gui_settings_shutdown_timeout_checkbox": "Brug timer med autostop", "gui_url_label_persistent": "Delingen stopper ikke automatisk, med mindre der er sat en timer.

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", - "gui_url_label_stay_open": "Delingen stopper ikke automatisk, med mindre der er sat en timer.", - "gui_url_label_onetime": "Delingen stopper efter den første download", + "gui_url_label_stay_open": "Delingen stopper ikke automatisk.", + "gui_url_label_onetime": "Delingen stopper efter den første download.", "gui_url_label_onetime_and_persistent": "Delingen stopper efter den første download

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", "gui_file_info": "{} filer, {}", "gui_file_info_single": "{} fil, {}", @@ -136,5 +136,40 @@ "gui_settings_whats_this": "Hvad er dette?", "gui_settings_general_label": "Generel opsætning", "gui_upload_in_progress": "Upload begyndte {}", - "gui_download_in_progress": "Download begyndte {}" + "gui_download_in_progress": "Download begyndte {}", + "gui_share_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", + "gui_receive_start_server": "Start modtage-tilstand", + "gui_receive_stop_server": "Stop modtage-tilstand", + "gui_receive_stop_server_shutdown_timeout": "Stop modtage-tilstand ({}s tilbage)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", + "gui_no_downloads": "Ingen downloads endnu", + "error_tor_protocol_error_unknown": "Der opstod en ukendt fejl med Tor", + "error_invalid_private_key": "Den private nøgletype understøttes ikke", + "gui_use_legacy_v2_onions_checkbox": "Brug forældede adresser", + "gui_status_indicator_share_stopped": "Klar til at dele", + "gui_status_indicator_share_working": "Starter…", + "gui_status_indicator_share_started": "Deler", + "gui_status_indicator_receive_stopped": "Klar til at modtage", + "gui_status_indicator_receive_working": "Starter…", + "gui_status_indicator_receive_started": "Modtager", + "receive_mode_received_file": "Modtaget: {}", + "gui_mode_share_button": "Del filer", + "gui_mode_receive_button": "Modtag filer", + "gui_settings_receiving_label": "Modtagelsesindstillinger", + "gui_settings_downloads_label": "Gem filer til", + "gui_settings_downloads_button": "Gennemse", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Modtage-tilstand kan ikke stoppes af afsenderen", + "gui_settings_public_mode_checkbox": "Offentlig tilstand", + "systray_close_server_title": "OnionShare-server lukket", + "systray_close_server_message": "En bruger lukkede serveren", + "systray_page_loaded_title": "OnionShare-side indlæst", + "systray_download_page_loaded_message": "En bruger indlæste downloadsiden", + "systray_upload_page_loaded_message": "En bruger indlæste uploadsiden", + "gui_uploads": "Uploadhistorik", + "gui_no_uploads": "Ingen uploads endnu", + "gui_clear_history": "Ryd alle", + "gui_upload_finished_range": "Uploadede {} til {}", + "gui_upload_finished": "Uploadet {}", + "gui_settings_language_label": "Foretrukne sprog", + "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft." } -- cgit v1.2.3-54-g00ecf From d3b52b62fad8feb5be4fac7633d4ffd018ddf52f Mon Sep 17 00:00:00 2001 From: communia Date: Fri, 16 Nov 2018 09:04:24 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index b11fec5c..95305bb4 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -62,7 +62,7 @@ "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", "gui_quit_warning_quit": "Sair", "gui_quit_warning_dont_quit": "Cancelar", - "error_rate_limit": "Alguém tentou por várias vezes entrar no seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", "zip_progress_bar_format": "Comprimindo: %p%", "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", @@ -117,5 +117,16 @@ "error_tor_protocol_error": "Houve um erro com Tor: {}", "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", "update_not_available": "Você está rodando a última versão de OnionShare.", - "gui_tor_connection_lost": "Desconectado do Tor." + "gui_tor_connection_lost": "Desconectado do Tor.", + "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", + "gui_settings_connection_type_socket_file_option": "Conectar usando um arquivo socket", + "gui_settings_connection_type_test_button": "Testar a conexão a Tor", + "gui_settings_control_port_label": "Entrada de controle", + "gui_settings_socket_file_label": "Arquivo socket", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)", + "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", + "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?" } -- cgit v1.2.3-54-g00ecf From f185d3d3a372f612abe602912e542e401de3ad4e Mon Sep 17 00:00:00 2001 From: ssantos Date: Sun, 18 Nov 2018 08:25:39 +0000 Subject: Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ --- share/locale/pt.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/share/locale/pt.json b/share/locale/pt.json index 95305bb4..4af05f42 100644 --- a/share/locale/pt.json +++ b/share/locale/pt.json @@ -1,7 +1,7 @@ { "give_this_url": "Dar este endereço ao destinatário:", "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", - "not_a_file": "{0:s} não é um arquivo válido.", + "not_a_file": "{0:s} não é um ficheiro válido.", "gui_copied_url": "URL foi copiado na área de transferência", "other_page_loaded": "Endereço carregado", "config_onion_service": "Configurando o serviço onion na entrada {0:d}", @@ -9,7 +9,7 @@ "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", "give_this_url_receive": "Dar este endereço ao remetente:", "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", - "not_a_readable_file": "{0:s} não é um arquivo legível.", + "not_a_readable_file": "{0:s} não é um ficheiro legível.", "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", "closing_automatically": "Interrompido porque o download terminou", @@ -84,8 +84,8 @@ "gui_settings_socks_label": "Entrada SOCKS", "gui_settings_authenticate_label": "Configurações de autenticação do Tor", "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", - "gui_settings_authenticate_password_option": "Senha", - "gui_settings_password_label": "Senha", + "gui_settings_authenticate_password_option": "Palavra-passe", + "gui_settings_password_label": "Palavra-passe", "gui_settings_tor_bridges": "Ajuda para pontes Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", @@ -110,8 +110,8 @@ "gui_status_indicator_receive_working": "Começando…", "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", - "gui_file_info_single": "{} arquivo, {}", - "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a senha esteja incorreta, ou o seu usuário não possui autorização para ler o arquivo de cookie.", + "gui_file_info_single": "{} ficheiro, {}", + "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.", "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", "error_tor_protocol_error": "Houve um erro com Tor: {}", @@ -119,10 +119,10 @@ "update_not_available": "Você está rodando a última versão de OnionShare.", "gui_tor_connection_lost": "Desconectado do Tor.", "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", - "gui_settings_connection_type_socket_file_option": "Conectar usando um arquivo socket", + "gui_settings_connection_type_socket_file_option": "Conectar usando um ficheiro socket", "gui_settings_connection_type_test_button": "Testar a conexão a Tor", "gui_settings_control_port_label": "Entrada de controle", - "gui_settings_socket_file_label": "Arquivo socket", + "gui_settings_socket_file_label": "Ficheiro socket", "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", -- cgit v1.2.3-54-g00ecf From f1cea2bb718b310ee86c4e3cdc2b7fc58c972baf Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Tue, 20 Nov 2018 17:41:51 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index 90233582..dd7c4073 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -21,11 +21,11 @@ "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Brug ikke Tor (kun til udvikling)", "help_stay_open": "Bliv ved med at dele efter første download", - "help_shutdown_timeout": "Luk ned for onion-tjenesten efter N sekunder", - "help_stealth": "Opret skjult onion-tjeneste (avanceret)", - "help_debug": "Log programfejl til stdout, og log webfejl til disk", + "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", + "help_stealth": "Brug klient-autentifikation (avanceret)", + "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", - "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", + "help_config": "Tilpasset placering af JSON-konfigurationsfil (valgfri)", "gui_drag_and_drop": "Træk og slip filer og mapper her\nfor at starte deling", "gui_add": "Tilføj", "gui_delete": "Slet", @@ -47,7 +47,7 @@ "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Annuller", "error_rate_limit": "En angriber forsøger måske at gætte din adresse. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye adresse.", - "zip_progress_bar_format": "Databehandler filer: %p%", + "zip_progress_bar_format": "Komprimerer: %p%", "error_stealth_not_supported": "For at oprette skjulte onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", @@ -61,7 +61,7 @@ "gui_settings_sharing_label": "Delingsindstillinger", "gui_settings_close_after_first_download_option": "Stop deling efter første download", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", - "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", + "gui_settings_connection_type_bundled_option": "Brug Tor-versionen som er indbygget i OnionShare", "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport", "gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil", @@ -77,7 +77,7 @@ "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbyggede obfs4 udskiftelige transporter", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbyggede obfs4 udskiftelige transporter (kræver obfs4proxy)", - "gui_settings_tor_bridges_custom_radio_option": "Brug brugerdefinerede broer", + "gui_settings_tor_bridges_custom_radio_option": "Brug tilpassede broer", "gui_settings_tor_bridges_custom_label": "Du kan få broer fra https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "Ingen af de broer du leverede ser ud til at være gyldige.\nPrøv venligst igen med gyldige broer.", "gui_settings_button_save": "Gem", @@ -86,15 +86,15 @@ "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da dine indstillingerne ikke giver mening.", - "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.", + "settings_error_automatic": "Kunne ikke oprette forbindelse til Tor-kontrolleren. Kører Tor Browser (tilgængelige fra torproject.org) i baggrunden?", "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontrolleren på {}:{}.", "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontrolleren med sokkelfilen {}.", "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?", "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.", - "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", + "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontrolleren, men adgangskoden kan være forkert, eller din bruger har ikke tilladelse til at læse cookiefilen.", "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller macOS.", "settings_error_bundled_tor_timeout": "Det tager for lang tid at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", - "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", + "settings_error_bundled_tor_broken": "OnionShare kunne ikke oprette forbindelse til Tor i baggrunden:\n{}", "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter skjulte onion-tjenester: {}", "error_tor_protocol_error": "Der opstod en fejl med Tor: {}", "connecting_to_tor": "Opretter forbindelse til Tor-netværket", @@ -105,23 +105,23 @@ "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afslut", - "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillinger.", + "gui_tor_connection_error_settings": "Prøv at ændre måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillingerne.", "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.", "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende adresse\n(fravalg vil slette gemt adresse)", + "gui_save_private_key_checkbox": "Brug en vedvarende adresse (udgået)", "gui_copied_url_title": "Kopierede OnionShare-adresse", "gui_copied_hidservauth_title": "Kopierede HidServAuth", - "gui_quit_title": "Igangværende overførsel", + "gui_quit_title": "Klap lige hesten", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Brug indbyggede meek_lite (Azure) udskiftelige transporter", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Brug indbyggede meek_lite (Azure) udskiftelige transporter (kræver obfs4proxy)", "gui_settings_shutdown_timeout_checkbox": "Brug timer med autostop", - "gui_url_label_persistent": "Delingen stopper ikke automatisk, med mindre der er sat en timer.

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", + "gui_url_label_persistent": "Delingen stopper ikke automatisk.

    Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).", "gui_url_label_stay_open": "Delingen stopper ikke automatisk.", "gui_url_label_onetime": "Delingen stopper efter den første download.", - "gui_url_label_onetime_and_persistent": "Delingen stopper efter den første download

    Hver deling har den samme adresse (hvis du vil bruge engangsadresser, så deaktivér vedvarende, i indstillingerne)", + "gui_url_label_onetime_and_persistent": "Delingen stopper ikke automatisk.

    Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).", "gui_file_info": "{} filer, {}", "gui_file_info_single": "{} fil, {}", "info_in_progress_downloads_tooltip": "{} igangværende downloads", @@ -171,5 +171,18 @@ "gui_upload_finished_range": "Uploadede {} til {}", "gui_upload_finished": "Uploadet {}", "gui_settings_language_label": "Foretrukne sprog", - "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft." + "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft.", + "gui_settings_meek_lite_expensive_warning": "Advarsel: meek_lite-broerne er meget dyre at køre for Tor-projektet.

    Brug dem kun hvis du ikke er i stand til at oprette forbindelse til Tor direkte, via obfs4-transporter eller andre normale broer.", + "gui_share_url_description": "Alle med OnionShare-adressen kan downloade dine filer med Tor Browser: ", + "gui_receive_url_description": "Alle med OnionShare-adressen kan uploade filer til din computer med Tor Browser: ", + "history_in_progress_tooltip": "{} igangværende", + "history_completed_tooltip": "{} færdige", + "info_in_progress_uploads_tooltip": "{} igangværende upload(s)", + "info_completed_uploads_tooltip": "{} upload(s) færdige", + "error_cannot_create_downloads_dir": "Kunne ikke oprette modtage-tilstand-mappe: {}", + "receive_mode_downloads_dir": "Filer som sendes til dig vises i denne mappe: {}", + "receive_mode_warning": "Advarsel: Modtage-tilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "gui_receive_mode_warning": "Modtage-tilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", + "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}" } -- cgit v1.2.3-54-g00ecf From b6c99999e3ee561d91df5b9de7c33c1449586f5f Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Tue, 20 Nov 2018 18:09:42 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index dd7c4073..5f198098 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -22,7 +22,7 @@ "help_local_only": "Brug ikke Tor (kun til udvikling)", "help_stay_open": "Bliv ved med at dele efter første download", "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", - "help_stealth": "Brug klient-autentifikation (avanceret)", + "help_stealth": "Brug klientautentifikation (avanceret)", "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", "help_config": "Tilpasset placering af JSON-konfigurationsfil (valgfri)", @@ -46,13 +46,13 @@ "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?", "gui_quit_warning_quit": "Afslut", "gui_quit_warning_dont_quit": "Annuller", - "error_rate_limit": "En angriber forsøger måske at gætte din adresse. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye adresse.", + "error_rate_limit": "Nogen har foretaget for mange forkerte forsøg på din adresse, hvilket kan betyde at de prøver at gætte det, så OnionShare har stoppet serveren. Start deling igen og send en ny adresse til modtageren for at dele.", "zip_progress_bar_format": "Komprimerer: %p%", - "error_stealth_not_supported": "For at oprette skjulte onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", + "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_option": "Opret skjulte onion-tjenester", - "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", + "gui_settings_stealth_option": "Brug klientautentifikation (forældet)", + "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu\nkan klikke for at kopiere din HidServAuth.", "gui_settings_autoupdate_label": "Søg efter ny version", "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", @@ -79,7 +79,7 @@ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbyggede obfs4 udskiftelige transporter (kræver obfs4proxy)", "gui_settings_tor_bridges_custom_radio_option": "Brug tilpassede broer", "gui_settings_tor_bridges_custom_label": "Du kan få broer fra https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Ingen af de broer du leverede ser ud til at være gyldige.\nPrøv venligst igen med gyldige broer.", + "gui_settings_tor_bridges_invalid": "Ingen at de broer du tilføjede virker.\nDobbeltklik på dem eller tilføj andre.", "gui_settings_button_save": "Gem", "gui_settings_button_cancel": "Annuller", "gui_settings_button_help": "Hjælp", @@ -92,21 +92,21 @@ "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?", "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.", "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontrolleren, men adgangskoden kan være forkert, eller din bruger har ikke tilladelse til at læse cookiefilen.", - "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller macOS.", - "settings_error_bundled_tor_timeout": "Det tager for lang tid at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", + "settings_error_bundled_tor_not_supported": "Brug af Tor-versionen som kom med OnionShare virker ikke i udviklertilstand på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "For længe om at oprette forbindelse til Tor. Måske har du ikke forbindelse til internettet, eller går dit systems ur forkert?", "settings_error_bundled_tor_broken": "OnionShare kunne ikke oprette forbindelse til Tor i baggrunden:\n{}", - "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter skjulte onion-tjenester: {}", + "settings_test_success": "Forbundet til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}.\nUnderstøtter klientautentifikation: {}.\nUnderstøtter næste generations .onion-adresser: {}.", "error_tor_protocol_error": "Der opstod en fejl med Tor: {}", "connecting_to_tor": "Opretter forbindelse til Tor-netværket", - "update_available": "Der findes en opdatering til OnionShare. Klik her for at downloade den.

    Installeret version: {}
    Seneste version: {}", - "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.", - "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.", + "update_available": "Der findes en ny OnionShare. Klik her for at hente den.

    Du bruger {} og den seneste er {}.", + "update_error_check_error": "Kunne ikke søge efter nye versioner: OnionShare-webstedet siger at den seneste version er den ugenkendte '{}'…", + "update_error_invalid_latest_version": "Kunne ikke søge efter ny version: Måske har du ikke forbindelse til Tor, eller er OnionShare-webstedet nede?", "update_not_available": "Du kører den seneste OnionShare.", - "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", + "gui_tor_connection_ask": "Åbn indstillingerne for at rette forbindelsen til Tor?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afslut", "gui_tor_connection_error_settings": "Prøv at ændre måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillingerne.", - "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", + "gui_tor_connection_canceled": "Kunne ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at opsætte dens forbindelse til Tor.", "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.", "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", @@ -138,9 +138,9 @@ "gui_upload_in_progress": "Upload begyndte {}", "gui_download_in_progress": "Download begyndte {}", "gui_share_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", - "gui_receive_start_server": "Start modtage-tilstand", - "gui_receive_stop_server": "Stop modtage-tilstand", - "gui_receive_stop_server_shutdown_timeout": "Stop modtage-tilstand ({}s tilbage)", + "gui_receive_start_server": "Start modtagetilstand", + "gui_receive_stop_server": "Stop modtagetilstand", + "gui_receive_stop_server_shutdown_timeout": "Stop modtagetilstand ({}s tilbage)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", "gui_no_downloads": "Ingen downloads endnu", "error_tor_protocol_error_unknown": "Der opstod en ukendt fejl med Tor", @@ -158,7 +158,7 @@ "gui_settings_receiving_label": "Modtagelsesindstillinger", "gui_settings_downloads_label": "Gem filer til", "gui_settings_downloads_button": "Gennemse", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "Modtage-tilstand kan ikke stoppes af afsenderen", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Modtagetilstand kan ikke stoppes af afsenderen", "gui_settings_public_mode_checkbox": "Offentlig tilstand", "systray_close_server_title": "OnionShare-server lukket", "systray_close_server_message": "En bruger lukkede serveren", @@ -179,10 +179,10 @@ "history_completed_tooltip": "{} færdige", "info_in_progress_uploads_tooltip": "{} igangværende upload(s)", "info_completed_uploads_tooltip": "{} upload(s) færdige", - "error_cannot_create_downloads_dir": "Kunne ikke oprette modtage-tilstand-mappe: {}", + "error_cannot_create_downloads_dir": "Kunne ikke oprette modtagetilstand-mappe: {}", "receive_mode_downloads_dir": "Filer som sendes til dig vises i denne mappe: {}", - "receive_mode_warning": "Advarsel: Modtage-tilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", - "gui_receive_mode_warning": "Modtage-tilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "receive_mode_warning": "Advarsel: Modtagetilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}" } -- cgit v1.2.3-54-g00ecf From 49d38cafccf7076d7869a5303c127b649c6fce86 Mon Sep 17 00:00:00 2001 From: DogeIlMeme Date: Thu, 22 Nov 2018 17:19:18 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index a40a813e..4f272c11 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -66,5 +66,6 @@ "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Preferenze", - "gui_settings_whats_this": "Cos'e' questo?" + "gui_settings_whats_this": "Cos'e' questo?", + "help_receive": "Ricevi condivisioni invece di inviarle" } -- cgit v1.2.3-54-g00ecf From 832309b4449f61ee4f216c7ed2e0bf03f77ad975 Mon Sep 17 00:00:00 2001 From: Heimen Stoffels Date: Sat, 24 Nov 2018 21:00:05 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 6bb1528a..f27bd1ea 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -1,28 +1,28 @@ { - "config_onion_service": "Onion service configureren op poort {0:d}.", - "preparing_files": "Bestanden om te delen aan het voorbereiden.", - "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", - "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", - "ctrlc_to_stop": "Druk Ctrl+C om de server te stoppen", - "not_a_file": "{0:s} is geen bestand.", + "config_onion_service": "Onion-dienst configureren op poort {0:d}.", + "preparing_files": "Bezig met comprimeren van bestanden.", + "give_this_url": "Geef dit adres aan de ontvanger:", + "give_this_url_stealth": "Geef dit adres en de HidServAuth-regel aan de ontvanger:", + "ctrlc_to_stop": "Druk op Ctrl+C om de server te stoppen", + "not_a_file": "{0:s} is geen geldig bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", - "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", - "other_page_loaded": "URL geladen", - "close_on_timeout": "Sluit automatisch omdat timeout bereikt is", - "closing_automatically": "Sluit automatisch omdat download gereed is", - "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten", - "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren", + "no_available_port": "Er is geen poort beschikbaar om de onion-dienst op te starten", + "other_page_loaded": "Adres geladen", + "close_on_timeout": "Gestopt omdat de automatische time-out bereikt is", + "closing_automatically": "Gestopt omdat de download is afgerond", + "timeout_download_still_running": "Bezig met wachten op afronden van download", + "large_filesize": "Waarschuwing: het versturen van grote bestanden kan uren duren", "systray_menu_exit": "Afsluiten", - "systray_download_started_title": "OnionShare download gestart", + "systray_download_started_title": "OnionShare-download gestart", "systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden", - "systray_download_completed_title": "OnionShare download gereed", + "systray_download_completed_title": "OnionShare-download afgerond", "systray_download_completed_message": "De gebruiker is klaar met downloaden", - "systray_download_canceled_title": "OnionShare download afgebroken", + "systray_download_canceled_title": "OnionShare-download afgebroken", "systray_download_canceled_message": "De gebruiker heeft de download afgebroken", - "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling", - "help_stay_open": "Laat verborgen service draaien nadat download gereed is", - "help_shutdown_timeout": "Sluit de Onion service na N seconden", - "help_stealth": "Maak stealth Onion service (geavanceerd)", + "help_local_only": "Tor niet gebruiken (alleen voor ontwikkelingsdoeleinden)", + "help_stay_open": "Blijven delen na afronden van eerste download", + "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", + "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", "help_debug": "Log fouten naar harde schijf", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Pad naar een JSON configuratie bestand (optioneel)", @@ -99,5 +99,10 @@ "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.", "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.", "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.", - "share_via_onionshare": "Deel via OnionShare" + "share_via_onionshare": "Deel via OnionShare", + "give_this_url_receive": "Geef dit adres aan de afzender:", + "give_this_url_receive_stealth": "Geef dit adres en de HidServAuth-regel aan de afzender:", + "systray_upload_started_title": "OnionShare-upload gestart", + "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer", + "help_receive": "Bestanden ontvangen in plaats van ze versturen" } -- cgit v1.2.3-54-g00ecf From da1c4df049879af624d30d47f2d78c03813f2510 Mon Sep 17 00:00:00 2001 From: DogeIlMeme Date: Sat, 24 Nov 2018 10:43:46 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index 4f272c11..87b873c7 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -67,5 +67,20 @@ "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Preferenze", "gui_settings_whats_this": "Cos'e' questo?", - "help_receive": "Ricevi condivisioni invece di inviarle" + "help_receive": "Ricevi condivisioni invece di inviarle", + "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", + "gui_settings_stealth_hidservauth_string": "Dopo aver salvato la tua chiave privata per il riutilizzo, significa che ora puoi\nclicca per copiare il tuo HidServAuth.", + "gui_settings_autoupdate_label": "controlla la nuova versione", + "gui_settings_autoupdate_option": "Notificami quando una nuova versione è disponibile", + "gui_settings_autoupdate_timestamp": "ultimo controllo", + "gui_settings_autoupdate_timestamp_never": "Mai", + "gui_settings_autoupdate_check_button": "Controlla per una nuova nuova versione", + "gui_settings_general_label": "Impostazioni generali", + "gui_settings_sharing_label": "Impostazione Condivise", + "gui_settings_close_after_first_download_option": "Ferma la condivisione dopo il primo download", + "gui_settings_connection_type_label": "Come OnionShare si connette a tor?", + "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", + "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", + "gui_settings_language_label": "Lingua preferita", + "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto." } -- cgit v1.2.3-54-g00ecf From a164d857764477c9f1e73d37e5caec9a78359a78 Mon Sep 17 00:00:00 2001 From: la corneja Date: Tue, 27 Nov 2018 21:09:08 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index a6abb43c..6a10fea2 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -8,7 +8,7 @@ "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", - "help_debug": "Fehler auf Festplatte schreiben", + "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler aus die Festplatte", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", "gui_add": "Hinzufügen", @@ -19,7 +19,7 @@ "gui_copy_url": "URL kopieren", "gui_downloads": "Bisherige Downloads", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", - "gui_please_wait": "Bitte warten...", + "gui_please_wait": "Starte... Klicken zum Abbrechen.", "timeout_download_still_running": "Warte auf Beendigung des Downloads", "systray_menu_exit": "Beenden", "gui_settings_authenticate_password_option": "Passwort", @@ -75,5 +75,98 @@ "settings_error_missing_password": "Mit dem Tor controller verbunden, aber er benötigt ein Passwort zur Authentifizierung.", "connecting_to_tor": "Verbinde mit dem Tornetzwerk", "gui_tor_connection_ask_quit": "Beenden", - "gui_tor_connection_lost": "Verbindung zu Tor getrennt." + "gui_tor_connection_lost": "Verbindung zu Tor getrennt.", + "help_stealth": "Nutze Klientauthorisierung (fortgeschritten)", + "gui_receive_start_server": "Starte den Empfängermodus", + "gui_receive_stop_server": "Stoppe den Empfängermodus", + "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {})", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", + "gui_no_downloads": "Bisher keine Downloads", + "gui_copied_url_title": "OnionShareadresse kopiert", + "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert", + "gui_download_upload_progress_complete": "%p%, {0:s} vergangen.", + "gui_download_upload_progress_starting": "{0:s}, %p% (berechne)", + "gui_download_upload_progress_eta": "{0:s}, Voraussichtliche Dauer: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Nicht so schnell", + "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", + "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", + "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", + "zip_progress_bar_format": "Packe: %p%", + "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", + "gui_settings_whats_this": "Was ist das?", + "gui_settings_stealth_option": "Nutze Klientauthorisierung (Alt)", + "gui_settings_autoupdate_label": "Suche nach neuerer Version", + "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", + "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", + "gui_settings_general_label": "Allgemeine Einstellungen", + "gui_settings_sharing_label": "Servereinstellungen", + "gui_settings_connection_type_automatic_option": "Versuche mit dem Tor Browser automatisch zu konfigurieren", + "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", + "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", + "gui_settings_tor_bridges": "Einstellungen für Tor bridges", + "gui_settings_meek_lite_expensive_warning": "Achtung: Die meek_lite bridges sind für das Tor Projekt sehr kostspielig.

    Nutze sie nur, wenn du dich nicht direkt, per obfs4 Transport oder über andere, normale bridges zum Tornetzwerk verbinden kannst.", + "gui_settings_tor_bridges_invalid": "Keine der bridges, die du angegeben hast, funktionieren.\nÜberprüfe sie oder gebe Andere an.", + "settings_error_unknown": "Kann nicht zum Tor controller verbinden, weil deine Einstellungen keinen Sinn ergeben.", + "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", + "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", + "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer hat keine Rechte, die Cookiedatei zu lesen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare gekoppelt ist nicht nutzen.", + "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", + "settings_error_bundled_tor_broken": "OnionShare konnte nicht zu Tor im Hintergrund verbinden:\n{}", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion Adressen der nächsten Generation: {}.", + "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", + "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", + "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", + "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", + "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShareSeite sagt, die letzte Version ist die unkenntliche '{}'…", + "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?", + "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.", + "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk zu ermöglichen?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.", + "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", + "share_via_onionshare": "Teile es per OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", + "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse (alt)", + "gui_share_url_description": "Jeder mit dieser OnionShareAdresse kann deine Dateien mit dem Tor Browser herunterladen: ", + "gui_receive_url_description": "Jeder mit dieser OnionShareAdresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", + "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.

    Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)", + "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.", + "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.", + "gui_status_indicator_share_working": "Starte…", + "gui_status_indicator_share_started": "Läuft", + "gui_status_indicator_receive_stopped": "Bereit zum Empfangen", + "gui_status_indicator_receive_working": "Starte…", + "gui_status_indicator_receive_started": "Empfange", + "gui_file_info": "{} Dateien, {}", + "gui_file_info_single": "{} Datei, {}", + "history_completed_tooltip": "{} vollständige", + "info_in_progress_uploads_tooltip": "{} Upload(s) laufen", + "info_completed_uploads_tooltip": "{} Upload(s) vollständig", + "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", + "receive_mode_downloads_dir": "Dateien, die dir geschickt werden, findest du in diesem Ordner: {}", + "receive_mode_warning": "Achtung: Im Empfängermodus können Leute Dateien auf deinen Computer laden. Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "gui_receive_mode_warning": "Im Empfängermodus können Leute Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "receive_mode_received_file": "Empfangen: {}", + "gui_mode_share_button": "Versende Dateien", + "gui_mode_receive_button": "Empfange Dateien", + "gui_settings_receiving_label": "Empfangseinstellungen", + "gui_settings_downloads_label": "Speichere Dateien in", + "gui_settings_downloads_button": "Durchsuchen", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Der Empfängermodus kann vom Versender gestoppt werden", + "gui_settings_public_mode_checkbox": "Öffentlich", + "systray_close_server_title": "OnionShareServer gestoppt", + "systray_close_server_message": "Ein Nutzer hat den Server gestoppt", + "systray_download_page_loaded_message": "Ein Nutzer hat die Downloadseite geöffnet", + "systray_upload_page_loaded_message": "Ein Nutzer hat die Uploadseite geöffnet", + "gui_uploads": "Uploadhistorie", + "gui_no_uploads": "Bisher keine Uploads", + "gui_clear_history": "Alle löschen", + "gui_upload_in_progress": "Upload gestartet {}", + "gui_download_in_progress": "Download gestartet {}", + "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", + "gui_settings_language_label": "Bevorzugte Sprache", + "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird." } -- cgit v1.2.3-54-g00ecf From 32e1cbad0895b55290a1ebf282f455ea62cdc7fe Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 1 Dec 2018 10:47:53 +0000 Subject: Added translation using Weblate (Portuguese (Brazil)) --- share/locale/pt_BR.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/pt_BR.json diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/pt_BR.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From e5c06432a5b894817ab0073c48328a3a28cabde2 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 1 Dec 2018 10:48:02 +0000 Subject: Added translation using Weblate (Portuguese (Portugal)) --- share/locale/pt_PT.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/pt_PT.json diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/pt_PT.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 71a9c2ae1c9fad10fe9754caa327d16ce900e407 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 08:33:22 +0100 Subject: Deleted translation using Weblate (Portuguese) --- share/locale/pt.json | 132 --------------------------------------------------- 1 file changed, 132 deletions(-) delete mode 100644 share/locale/pt.json diff --git a/share/locale/pt.json b/share/locale/pt.json deleted file mode 100644 index 4af05f42..00000000 --- a/share/locale/pt.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "give_this_url": "Dar este endereço ao destinatário:", - "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", - "not_a_file": "{0:s} não é um ficheiro válido.", - "gui_copied_url": "URL foi copiado na área de transferência", - "other_page_loaded": "Endereço carregado", - "config_onion_service": "Configurando o serviço onion na entrada {0:d}", - "preparing_files": "Comprimindo arquivos.", - "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", - "give_this_url_receive": "Dar este endereço ao remetente:", - "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", - "not_a_readable_file": "{0:s} não é um ficheiro legível.", - "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", - "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", - "closing_automatically": "Interrompido porque o download terminou", - "timeout_download_still_running": "Esperando que o download termine", - "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", - "systray_menu_exit": "Sair", - "systray_download_started_title": "O download de OnionShare começou", - "systray_download_started_message": "Alguém começou fazer o download dos seus arquivos", - "systray_download_completed_title": "O download de OnionShare terminou", - "systray_download_completed_message": "Essa pessoa terminou de fazer o download dos seus arquivos", - "systray_download_canceled_title": "O download de OnionShare foi cancelado", - "systray_download_canceled_message": "Essa pessoa cancelou o download", - "systray_upload_started_title": "OnionShare começou a carregar", - "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", - "help_local_only": "Não use Tor (unicamente para programação)", - "help_stay_open": "Continuar a compartilhar depois do primeiro download", - "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", - "help_stealth": "Usar autorização de cliente (avançado)", - "help_receive": "Receber compartilhamentos ao invés de enviá-los", - "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco", - "help_filename": "Lista de arquivos ou pastas a compartilhar", - "help_config": "Personalizar a configuração JSON de localização de arquivos (opcional)", - "gui_drag_and_drop": "Arrastar arquivos e pastas\npara começar a compartilhá-los", - "gui_add": "Adicionar", - "gui_delete": "Apagar", - "gui_choose_items": "Escolher", - "gui_share_start_server": "Começar a compartilhar", - "gui_share_stop_server": "Parar de compartilhar", - "gui_share_stop_server_shutdown_timeout": "Parar de compartilhar ({}segundos para terminar)", - "gui_share_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às", - "gui_receive_start_server": "Modo Começar a Receber", - "gui_receive_stop_server": "Modo Parar de Receber", - "gui_receive_stop_server_shutdown_timeout": "Modo Parar de Receber ({}segundos para terminar)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às {}", - "gui_copy_url": "Copiar endereço", - "gui_copy_hidservauth": "Copiar HidServAuth", - "gui_downloads": "Histórico de download", - "gui_no_downloads": "Nenhum download por enquanto", - "gui_canceled": "Cancelado", - "gui_copied_url_title": "Endereço OnionShare copiado", - "gui_copied_hidservauth_title": "HidServAuth copiado", - "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência", - "gui_please_wait": "Começando...Clique para cancelar.", - "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", - "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", - "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_quit_title": "Mais devagar", - "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?", - "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", - "gui_quit_warning_quit": "Sair", - "gui_quit_warning_dont_quit": "Cancelar", - "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", - "zip_progress_bar_format": "Comprimindo: %p%", - "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", - "gui_settings_window_title": "Configurações", - "gui_settings_whats_this": "O que é isso?", - "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", - "gui_settings_autoupdate_label": "Procurar a nova versão", - "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", - "gui_settings_autoupdate_timestamp": "Última atualização: {}", - "gui_settings_autoupdate_timestamp_never": "Nunca", - "gui_settings_autoupdate_check_button": "Procurar a nova versão", - "gui_settings_general_label": "Configurações gerais", - "gui_settings_sharing_label": "Compartilhando configurações", - "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", - "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", - "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", - "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", - "gui_settings_connection_type_control_port_option": "Conectar usando entrada de controle", - "gui_settings_socks_label": "Entrada SOCKS", - "gui_settings_authenticate_label": "Configurações de autenticação do Tor", - "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", - "gui_settings_authenticate_password_option": "Palavra-passe", - "gui_settings_password_label": "Palavra-passe", - "gui_settings_tor_bridges": "Ajuda para pontes Tor", - "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", - "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", - "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas", - "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.", - "gui_settings_button_save": "Salvar", - "gui_settings_button_cancel": "Cancelar", - "gui_settings_button_help": "Ajuda", - "gui_settings_shutdown_timeout_checkbox": "Usar cronômetro para encerrar automaticamente", - "gui_settings_shutdown_timeout": "Encerrar o compartilhamento às:", - "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", - "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor", - "error_invalid_private_key": "Este tipo de chave privada não possui suporte", - "connecting_to_tor": "Conectando à rede Tor", - "gui_tor_connection_ask_open_settings": "Sim", - "gui_tor_connection_ask_quit": "Sair", - "gui_status_indicator_share_stopped": "Pronto para compartilhar", - "gui_status_indicator_share_working": "Começando…", - "gui_status_indicator_share_started": "Compartilhando", - "gui_status_indicator_receive_stopped": "Pronto para receber", - "gui_status_indicator_receive_working": "Começando…", - "gui_status_indicator_receive_started": "Recebendo", - "gui_file_info": "{} arquivos, {}", - "gui_file_info_single": "{} ficheiro, {}", - "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.", - "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", - "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", - "error_tor_protocol_error": "Houve um erro com Tor: {}", - "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", - "update_not_available": "Você está rodando a última versão de OnionShare.", - "gui_tor_connection_lost": "Desconectado do Tor.", - "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", - "gui_settings_connection_type_socket_file_option": "Conectar usando um ficheiro socket", - "gui_settings_connection_type_test_button": "Testar a conexão a Tor", - "gui_settings_control_port_label": "Entrada de controle", - "gui_settings_socket_file_label": "Ficheiro socket", - "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)", - "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", - "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?" -} -- cgit v1.2.3-54-g00ecf From 1f0cb72974168eb4fbd158f6475ef166fd46826f Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:04 +0000 Subject: Added translation using Weblate (Arabic) --- share/locale/ar.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ar.json diff --git a/share/locale/ar.json b/share/locale/ar.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/ar.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From e34c4ab6514bbd6a445cec26ba3b14b5634fdd27 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:13 +0000 Subject: Added translation using Weblate (Indonesian) --- share/locale/id.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/id.json diff --git a/share/locale/id.json b/share/locale/id.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/id.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 360b6a369df61b5996e9543d7ab572ac966faa68 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:22 +0000 Subject: Added translation using Weblate (Macedonian) --- share/locale/mk.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/mk.json diff --git a/share/locale/mk.json b/share/locale/mk.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/mk.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From c53296749aa74df013588e9dbd1d9ea7e85b687c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:31 +0000 Subject: Added translation using Weblate (Polish) --- share/locale/pl.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/pl.json diff --git a/share/locale/pl.json b/share/locale/pl.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/pl.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 651bbb4e1c9156a61cb6eafd9672f3a9f6284a07 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:40 +0000 Subject: Added translation using Weblate (Slovenian) --- share/locale/sl.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/sl.json diff --git a/share/locale/sl.json b/share/locale/sl.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/sl.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 8a944f9e8c0a34565f0f496b638a94862abdd2b6 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:50 +0000 Subject: Added translation using Weblate (Tachelhit) --- share/locale/shi.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/shi.json diff --git a/share/locale/shi.json b/share/locale/shi.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/shi.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From e0f25c8d55541b1e4d1a94540c2a07e0dfc1b1f0 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:41:59 +0000 Subject: Added translation using Weblate (Wolof) --- share/locale/wo.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/wo.json diff --git a/share/locale/wo.json b/share/locale/wo.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/wo.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From d46d15b4ae6f250ecac56382d7a8263261479eb1 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:43:30 +0000 Subject: Added translation using Weblate (Chinese (Simplified)) --- share/locale/zh_Hans.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/zh_Hans.json diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/zh_Hans.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 8f2fad88577e0243950f032ef789b25cd3a6a316 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:43:40 +0000 Subject: Added translation using Weblate (Chinese (Traditional)) --- share/locale/zh_Hant.json | 185 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/zh_Hant.json diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/zh_Hant.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From be1d9b0dec6c0d0ed29d0fe3eb83d0d4819923dc Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:46:29 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index a67a5f75..7aaacd9e 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -14,15 +14,15 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "خروج", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", "systray_download_completed_message": "", "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", + "systray_download_canceled_message": "المستخدم الغاء التحميل", + "systray_upload_started_title": "onionshare تحميل بدأت", + "systray_upload_started_message": "بدأ المستخدم تحميل الملفات إلى جهاز الكمبيوتر الخاص بك", "help_local_only": "", "help_stay_open": "", "help_shutdown_timeout": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "حذف", + "gui_choose_items": "إختر", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", -- cgit v1.2.3-54-g00ecf From cc14693e652213c62d29fbb4e5021744db03c947 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sun, 2 Dec 2018 07:53:30 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index a67a5f75..0e95a29c 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -1,26 +1,26 @@ { "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", + "preparing_files": "Comprimint arxius.", + "give_this_url": "Dona aquesta adreça al destinatari:", "give_this_url_stealth": "", - "give_this_url_receive": "", + "give_this_url_receive": "Dona aquesta adreça al remitent:", "give_this_url_receive_stealth": "", "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "not_a_file": "{0:s} no és un arxiu vàlid.", + "not_a_readable_file": "{0:s} no és un arxiu llegible.", + "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei de ceba", + "other_page_loaded": "Adreça carregada", "close_on_timeout": "", "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Surt", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", "systray_download_completed_message": "", "systray_download_canceled_title": "", - "systray_download_canceled_message": "", + "systray_download_canceled_message": "L'usuari va cancel·lar la descàrrega", "systray_upload_started_title": "", "systray_upload_started_message": "", "help_local_only": "", -- cgit v1.2.3-54-g00ecf From cfd55fb34dfc6c720de0d0b4d0060144d69ce170 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 1 Dec 2018 10:49:03 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 266 ++++++++++++++++++++++++------------------------ 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index a67a5f75..6a89d825 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -1,139 +1,139 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", + "config_onion_service": "Configurando o serviço onion na entrada {0:d}", + "preparing_files": "Comprimindo arquivos.", + "give_this_url": "Dar este endereço ao destinatário:", + "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", + "give_this_url_receive": "Dar este endereço ao remetente:", + "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", + "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", + "not_a_file": "{0:s} não é um ficheiro válido.", + "not_a_readable_file": "{0:s} não é um ficheiro legível.", + "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", + "other_page_loaded": "Endereço carregado", + "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", + "closing_automatically": "Interrompido porque o download terminou", + "timeout_download_still_running": "Esperando que o download termine", + "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", + "systray_menu_exit": "Sair", + "systray_download_started_title": "O download de OnionShare começou", + "systray_download_started_message": "Alguém começou fazer o download dos seus arquivos", + "systray_download_completed_title": "O download de OnionShare terminou", + "systray_download_completed_message": "Essa pessoa terminou de fazer o download dos seus arquivos", + "systray_download_canceled_title": "O download de OnionShare foi cancelado", + "systray_download_canceled_message": "Essa pessoa cancelou o download", + "systray_upload_started_title": "OnionShare começou a carregar", + "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", + "help_local_only": "Não use Tor (unicamente para programação)", + "help_stay_open": "Continuar a compartilhar depois do primeiro download", + "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", + "help_stealth": "Usar autorização de cliente (avançado)", + "help_receive": "Receber compartilhamentos ao invés de enviá-los", + "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco", + "help_filename": "Lista de arquivos ou pastas a compartilhar", + "help_config": "Personalizar a configuração JSON de localização de arquivos (opcional)", + "gui_drag_and_drop": "Arrastar arquivos e pastas\npara começar a compartilhá-los", + "gui_add": "Adicionar", + "gui_delete": "Apagar", + "gui_choose_items": "Escolher", + "gui_share_start_server": "Começar a compartilhar", + "gui_share_stop_server": "Parar de compartilhar", + "gui_share_stop_server_shutdown_timeout": "Parar de compartilhar ({}segundos para terminar)", + "gui_share_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às", + "gui_receive_start_server": "Modo Começar a Receber", + "gui_receive_stop_server": "Modo Parar de Receber", + "gui_receive_stop_server_shutdown_timeout": "Modo Parar de Receber ({}segundos para terminar)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às {}", + "gui_copy_url": "Copiar endereço", + "gui_copy_hidservauth": "Copiar HidServAuth", + "gui_downloads": "Histórico de download", + "gui_no_downloads": "Nenhum download por enquanto", + "gui_canceled": "Cancelado", + "gui_copied_url_title": "Endereço OnionShare copiado", + "gui_copied_url": "URL foi copiado na área de transferência", + "gui_copied_hidservauth_title": "HidServAuth copiado", + "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência", + "gui_please_wait": "Começando...Clique para cancelar.", + "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Mais devagar", + "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?", + "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", + "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "zip_progress_bar_format": "Comprimindo: %p%", + "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", + "gui_settings_window_title": "Configurações", + "gui_settings_whats_this": "O que é isso?", + "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", + "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", + "gui_settings_autoupdate_label": "Procurar a nova versão", + "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", + "gui_settings_autoupdate_timestamp": "Última atualização: {}", + "gui_settings_autoupdate_timestamp_never": "Nunca", + "gui_settings_autoupdate_check_button": "Procurar a nova versão", + "gui_settings_general_label": "Configurações gerais", + "gui_settings_sharing_label": "Compartilhando configurações", + "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", + "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", + "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", + "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", + "gui_settings_connection_type_control_port_option": "Conectar usando entrada de controle", + "gui_settings_connection_type_socket_file_option": "Conectar usando um ficheiro socket", + "gui_settings_connection_type_test_button": "Testar a conexão a Tor", + "gui_settings_control_port_label": "Entrada de controle", + "gui_settings_socket_file_label": "Ficheiro socket", + "gui_settings_socks_label": "Entrada SOCKS", + "gui_settings_authenticate_label": "Configurações de autenticação do Tor", + "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", + "gui_settings_authenticate_password_option": "Palavra-passe", + "gui_settings_password_label": "Palavra-passe", + "gui_settings_tor_bridges": "Ajuda para pontes Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", + "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas", + "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.", + "gui_settings_button_save": "Salvar", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", + "gui_settings_shutdown_timeout_checkbox": "Usar cronômetro para encerrar automaticamente", + "gui_settings_shutdown_timeout": "Encerrar o compartilhamento às:", + "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", + "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?", + "settings_error_socket_port": "Não pode ligar ao controlador do Tor em {}:{}.", "settings_error_socket_file": "", "settings_error_auth": "", "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", + "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.", + "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", + "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", + "settings_error_bundled_tor_broken": "OnionShare não pôde se conectar a Tor em segundo plano:\n{}", + "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", + "error_tor_protocol_error": "Houve um erro com Tor: {}", + "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor", + "error_invalid_private_key": "Este tipo de chave privada não possui suporte", + "connecting_to_tor": "Conectando à rede Tor", + "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", "update_error_check_error": "", "update_error_invalid_latest_version": "", - "update_not_available": "", + "update_not_available": "Você está rodando a última versão de OnionShare.", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", + "gui_tor_connection_lost": "Desconectado do Tor.", "gui_server_started_after_timeout": "", "gui_server_timeout_expired": "", - "share_via_onionshare": "", + "share_via_onionshare": "Compartilhar por meio de OnionShare", "gui_use_legacy_v2_onions_checkbox": "", "gui_save_private_key_checkbox": "", "gui_share_url_description": "", @@ -142,14 +142,14 @@ "gui_url_label_stay_open": "", "gui_url_label_onetime": "", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", + "gui_status_indicator_share_stopped": "Pronto para compartilhar", + "gui_status_indicator_share_working": "Começando…", + "gui_status_indicator_share_started": "Compartilhando", + "gui_status_indicator_receive_stopped": "Pronto para receber", + "gui_status_indicator_receive_working": "Começando…", + "gui_status_indicator_receive_started": "Recebendo", + "gui_file_info": "{} arquivos, {}", + "gui_file_info_single": "{} ficheiro, {}", "history_in_progress_tooltip": "", "history_completed_tooltip": "", "info_in_progress_uploads_tooltip": "", -- cgit v1.2.3-54-g00ecf From 358a7c68e35f4725e2ec57c49288bcd3637de74f Mon Sep 17 00:00:00 2001 From: xin Date: Tue, 4 Dec 2018 14:21:48 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 09bc9681..d9189590 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,18 +1,18 @@ { - "preparing_files": "Compression de fichiers.", + "preparing_files": "Compression des fichiers.", "give_this_url": "Donnez cette adresse au destinataire :", "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", "not_a_file": "{0:s} n'est pas un fichier valide.", "other_page_loaded": "Adresse chargée", "closing_automatically": "Arrêt automatique car le téléchargement est fini", "systray_menu_exit": "Quitter", - "systray_download_started_title": "Téléchargement OnionShare Démarré", + "systray_download_started_title": "Téléchargement OnionShare démarré", "systray_download_started_message": "Un utilisateur télécharge vos fichiers", "systray_download_completed_title": "Téléchargement OnionShare terminé", "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", - "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", + "help_stay_open": "Continuer le partage après le premier téléchargement", "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs Web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", @@ -31,7 +31,7 @@ "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", - "config_onion_service": "Mise en place du service de l'oignon sur le port {0:d}.", + "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", @@ -62,5 +62,6 @@ "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p%", "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0.", - "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes" + "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", + "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor." } -- cgit v1.2.3-54-g00ecf From 37423ed213fee7fe89383c3cd9ee2d5ab76b49ab Mon Sep 17 00:00:00 2001 From: emma peel Date: Tue, 4 Dec 2018 14:26:06 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index d9189590..6ea8118f 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -63,5 +63,7 @@ "zip_progress_bar_format": "Compression : %p%", "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0.", "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", - "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor." + "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", + "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", + "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restants)" } -- cgit v1.2.3-54-g00ecf From 8eb29e00132a195e6146f43365e06340e5ffc819 Mon Sep 17 00:00:00 2001 From: xin Date: Tue, 4 Dec 2018 14:41:21 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 6ea8118f..a2530fee 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -26,7 +26,7 @@ "gui_downloads": "Historique de téléchargement", "gui_canceled": "Annulé", "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", - "gui_please_wait": "Démarrage … Cliquez pour annuler.", + "gui_please_wait": "Démarrage… Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", @@ -65,5 +65,83 @@ "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", - "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restants)" + "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)", + "systray_upload_started_title": "Envoi OnionShare démarré", + "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur", + "gui_no_downloads": "Pas encore de téléchargements", + "gui_copied_url_title": "Adresse OnionShare copiée", + "gui_quit_title": "Pas si vite", + "gui_share_quit_warning": "Vous êtes en train d'envoyer des fichiers. Voulez-vous vraiment quitter OnionShare ?", + "gui_receive_quit_warning": "Vous êtes en train de recevoir des fichiers. Voulez-vous vraiment quitter OnionShare ?", + "gui_settings_whats_this": "Qu'est-ce que c'est ?", + "gui_settings_autoupdate_label": "Rechercher des mises à jour", + "gui_settings_autoupdate_option": "Me notifier lorsque des mises à jour sont disponibles", + "gui_settings_general_label": "Paramètres généraux", + "gui_settings_sharing_label": "Paramètres de partage", + "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor inclue dans OnionShare", + "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le navigateur Tor", + "gui_settings_connection_type_test_button": "Tester la connexion à Tor", + "gui_settings_control_port_label": "Port de contrôle", + "gui_settings_authenticate_label": "Paramètres d'authentification de Tor", + "gui_settings_tor_bridges": "Support des bridges Tor", + "gui_settings_tor_bridges_custom_radio_option": "Utiliser des bridges personnalisés", + "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des bridges à l'adresse https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Aucun des bridges que vous avez ajouté ne fonctionne.\nVérifiez les à nouveau ou ajoutez-en d'autres.", + "settings_error_unknown": "Impossible de se connecter au contrôleur Tor car les paramètres n'ont pas de sens.", + "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Est-ce que le navigateur Tor (disponible sur torproject.org) fonctionne en arrière-plan ?", + "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", + "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier socket {}.", + "settings_error_auth": "Connecté à {}:{} mais impossible de s'authentifier. Peut-être que ce n'est pas un contrôleur Tor ?", + "settings_error_missing_password": "Connecté au contrôleur Tor mais il demande un mot de passe pour s'authentifier.", + "settings_error_unreadable_cookie_file": "Connecté au contrôleur Tor, mais le mot de passe est peut-être faux ou l'utilisateur n'a pas la permission de lire le fichier cookie.", + "settings_error_bundled_tor_not_supported": "L'utilisation de la version de Tor inclue avec OnionShare ne marche pas dans le mode développement sous Windows ou macOS.", + "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Peut-être qu'il n'y a pas de connexion à Internet ou que l'horloge système est inexacte ?", + "settings_error_bundled_tor_broken": "OnionShare n'a pas pu se connecter à Tor en arrière-plan :\n{}", + "error_tor_protocol_error": "Il y a eu une erreur avec Tor : {}", + "error_tor_protocol_error_unknown": "Il y a eu une erreur inconnue avec Tor", + "error_invalid_private_key": "Ce type de clé privée n'est pas supporté", + "update_available": "Une nouvelle version de OnionShare est disponible. Cliquez ici pour l'obtenir.

    Vous utilisez actuellement la version {} et la dernière version est la {}.", + "update_not_available": "Vous utilisez la dernière version d'OnionShare.", + "gui_tor_connection_ask_open_settings": "Oui", + "gui_tor_connection_ask_quit": "Quitter", + "gui_tor_connection_lost": "Déconnecté de Tor.", + "share_via_onionshare": "Partager via OnionShare", + "gui_save_private_key_checkbox": "Utiliser une adresse persistante (legacy)", + "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", + "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", + "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_url_label_stay_open": "Ce partage ne s'arrêtera pas automatiquement.", + "gui_url_label_onetime": "Ce partage s'arrêtera après le premier téléchargement complété.", + "gui_url_label_onetime_and_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants devraient réutiliser l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_status_indicator_share_stopped": "Prêt à partager", + "gui_status_indicator_share_working": "Démarrage…", + "gui_status_indicator_share_started": "Partage", + "gui_status_indicator_receive_stopped": "Prêt à recevoir", + "gui_status_indicator_receive_working": "Démarrage…", + "gui_status_indicator_receive_started": "Réception", + "gui_file_info": "{} fichiers, {}", + "gui_file_info_single": "{} fichier, {}", + "history_in_progress_tooltip": "{} en cours", + "history_completed_tooltip": "{} terminé", + "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", + "receive_mode_warning": "Avertissement : le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_received_file": "Reçu : {}", + "gui_mode_share_button": "Fichiers partagés", + "gui_mode_receive_button": "Fichiers reçus", + "gui_settings_receiving_label": "Paramètres de réception", + "gui_settings_downloads_label": "Enregistrer les fichiers sous", + "gui_settings_downloads_button": "Parcourir", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Le mode réception peut-être arrêté par l'expéditeur", + "gui_settings_public_mode_checkbox": "Mode public", + "systray_close_server_title": "Serveur OnionShare arrêté", + "gui_uploads": "Historique d'envoi", + "gui_no_uploads": "Pas encore d'envoi", + "gui_clear_history": "Tout effacer", + "gui_upload_in_progress": "Envoi démarré {}", + "gui_upload_finished_range": "Envoyé {} de {}", + "gui_upload_finished": "{} envoyé", + "gui_download_in_progress": "Téléchargement démarré {}", + "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", + "gui_settings_language_label": "Langue préférée" } -- cgit v1.2.3-54-g00ecf From e6167cd6046647b524367752697c2fd7a6993dbd Mon Sep 17 00:00:00 2001 From: emma peel Date: Tue, 4 Dec 2018 15:59:10 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index a2530fee..a301bf5b 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -143,5 +143,7 @@ "gui_upload_finished": "{} envoyé", "gui_download_in_progress": "Téléchargement démarré {}", "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", - "gui_settings_language_label": "Langue préférée" + "gui_settings_language_label": "Langue préférée", + "help_stealth": "Utilisation de l'autorisation du client (avancé)", + "help_receive": "Recevoir des actions au lieu de les envoyer" } -- cgit v1.2.3-54-g00ecf From 9c3d668679cd38aee73995eadd431715135e0044 Mon Sep 17 00:00:00 2001 From: xin Date: Tue, 4 Dec 2018 15:59:45 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index a301bf5b..eecf1481 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -13,7 +13,7 @@ "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Continuer le partage après le premier téléchargement", - "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs Web sur le disque", + "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", "gui_add": "Ajouter", @@ -144,6 +144,15 @@ "gui_download_in_progress": "Téléchargement démarré {}", "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", "gui_settings_language_label": "Langue préférée", - "help_stealth": "Utilisation de l'autorisation du client (avancé)", - "help_receive": "Recevoir des actions au lieu de les envoyer" + "help_stealth": "Utilisation de l'autorisation client (avancé)", + "help_receive": "Recevoir des partages au lieu de les envoyer", + "gui_receive_start_server": "Démarrer le mode réception", + "gui_receive_stop_server": "Arrêter le mode réception", + "gui_receive_stop_server_shutdown_timeout": "Arrêter le mode réception ({}s restantes)", + "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", + "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", + "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", + "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", + "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", + "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)" } -- cgit v1.2.3-54-g00ecf From 6b315fd1be382fc8d3e5a83dd28bedef1057c2f0 Mon Sep 17 00:00:00 2001 From: la corneja Date: Wed, 5 Dec 2018 14:04:34 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 6a10fea2..30f5eed0 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,6 +1,6 @@ { "preparing_files": "Dateien werden vorbereitet.", - "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", + "give_this_url": "Gib diese URL an den Empfänger:", "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", "other_page_loaded": "URL geladen", @@ -97,7 +97,7 @@ "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", "gui_settings_stealth_option": "Nutze Klientauthorisierung (Alt)", - "gui_settings_autoupdate_label": "Suche nach neuerer Version", + "gui_settings_autoupdate_label": "Suche nach einer neueren Version", "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", "gui_settings_general_label": "Allgemeine Einstellungen", @@ -168,5 +168,6 @@ "gui_download_in_progress": "Download gestartet {}", "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", "gui_settings_language_label": "Bevorzugte Sprache", - "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird." + "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", + "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)" } -- cgit v1.2.3-54-g00ecf From a6cdd5a76b84ac7dde2a667dc08cea6a182313c4 Mon Sep 17 00:00:00 2001 From: Kevin Scannell Date: Mon, 3 Dec 2018 13:03:22 +0000 Subject: Translated using Weblate (Irish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ga/ --- share/locale/ga.json | 366 +++++++++++++++++++++++++-------------------------- 1 file changed, 183 insertions(+), 183 deletions(-) diff --git a/share/locale/ga.json b/share/locale/ga.json index a67a5f75..114661d2 100644 --- a/share/locale/ga.json +++ b/share/locale/ga.json @@ -1,185 +1,185 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "config_onion_service": "Seirbhís onion á shocrú ar phort {0:d}.", + "preparing_files": "Comhaid á gcomhbhrú.", + "give_this_url": "Tabhair an seoladh seo don fhaighteoir:", + "give_this_url_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don fhaighteoir:", + "give_this_url_receive": "Tabhair an seoladh seo don seoltóir:", + "give_this_url_receive_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don seoltóir:", + "ctrlc_to_stop": "Brúigh Ctrl+C chun stop a chur leis an bhfreastalaí", + "not_a_file": "Ní comhad bailí é {0:s}.", + "not_a_readable_file": "Ní comhad inléite é {0:s}.", + "no_available_port": "Níorbh fhéidir port a aimsiú chun an tseirbhís onion a thosú", + "other_page_loaded": "Seoladh lódáilte", + "close_on_timeout": "Cuireadh stop leis toisc go bhfuil an t-amadóir caite", + "closing_automatically": "Cuireadh stop leis toisc go bhfuil an íoslódáil críochnaithe", + "timeout_download_still_running": "Ag fanacht go gcríochnódh an íoslódáil", + "large_filesize": "Rabhadh: D'fhéadfadh go dtógfadh sé tamall fada comhad mór a sheoladh", + "systray_menu_exit": "Scoir", + "systray_download_started_title": "Tosaíodh Íoslódáil OnionShare", + "systray_download_started_message": "Thosaigh úsáideoir ag íoslódáil do chuid comhad", + "systray_download_completed_title": "Críochnaíodh Íoslódáil OnionShare", + "systray_download_completed_message": "Tá do chuid comhad íoslódáilte ag an úsáideoir", + "systray_download_canceled_title": "Cuireadh Íoslódáil OnionShare ar ceal", + "systray_download_canceled_message": "Chuir an t-úsáideoir an íoslódáil ar ceal", + "systray_upload_started_title": "Tosaíodh Uaslódáil OnionShare", + "systray_upload_started_message": "Thosaigh úsáideoir ag uaslódáil comhad go dtí do ríomhaire", + "help_local_only": "Ná húsáid Tor (tástáil amháin)", + "help_stay_open": "Lean ort ag comhroinnt tar éis an chéad íoslódáil", + "help_shutdown_timeout": "Stop ag comhroinnt tar éis líon áirithe soicindí", + "help_stealth": "Úsáid údarú cliaint (ardleibhéal)", + "help_receive": "Glac le comhaid chomhroinnte in áit iad a sheoladh", + "help_debug": "Déan tuairisc ar earráidí OnionShare ar stdout, agus earráidí Gréasáin ar an diosca", + "help_filename": "Liosta comhad nó fillteán le comhroinnt", + "help_config": "Suíomh saincheaptha don chomhad cumraíochta JSON (roghnach)", + "gui_drag_and_drop": "Tarraing agus scaoil comhaid agus fillteáin\nchun iad a chomhroinnt", + "gui_add": "Cuir Leis", + "gui_delete": "Scrios", + "gui_choose_items": "Roghnaigh", + "gui_share_start_server": "Comhroinn", + "gui_share_stop_server": "Stop ag comhroinnt", + "gui_share_stop_server_shutdown_timeout": "Stop ag Comhroinnt ({}s fágtha)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}", + "gui_receive_start_server": "Tosaigh an Mód Glactha", + "gui_receive_stop_server": "Stop an Mód Glactha", + "gui_receive_stop_server_shutdown_timeout": "Stop an Mód Glactha ({}s fágtha)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}", + "gui_copy_url": "Cóipeáil an Seoladh", + "gui_copy_hidservauth": "Cóipeáil HidServAuth", + "gui_downloads": "Stair Íoslódála", + "gui_no_downloads": "Níl aon rud íoslódáilte agat fós", + "gui_canceled": "Curtha ar ceal", + "gui_copied_url_title": "Cóipeáladh an Seoladh OnionShare", + "gui_copied_url": "Cóipeáladh an seoladh OnionShare go dtí an ghearrthaisce", + "gui_copied_hidservauth_title": "Cóipeáladh HidServAuth", + "gui_copied_hidservauth": "Cóipeáladh an líne HidServAuth go dtí an ghearrthaisce", + "gui_please_wait": "Ag tosú... Cliceáil lena chur ar ceal.", + "gui_download_upload_progress_complete": "%[%, {0:s} caite.", + "gui_download_upload_progress_starting": "{0:s}, %p% (á áireamh)", + "gui_download_upload_progress_eta": "{0:s}, am teachta measta: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Fan soic", + "gui_share_quit_warning": "Tá tú le linn roinnt comhad a sheoladh. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?", + "gui_receive_quit_warning": "Tá tú le linn roinnt comhad a íoslódáil. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?", + "gui_quit_warning_quit": "Scoir", + "gui_quit_warning_dont_quit": "Cealaigh", + "error_rate_limit": "Rinne duine éigin an iomarca iarrachtaí míchearta ar do sheoladh, agus dá bharr sin stop OnionShare an freastalaí. Tosaigh ag comhroinnt arís agus cuir seoladh nua chuig an bhfaighteoir.", + "zip_progress_bar_format": "Á chomhbhrú: %p%", + "error_stealth_not_supported": "Chun údarú cliaint a úsáid, teastaíonn uait Tor 0.2.9.1-alpha (nó Brabhsálaí 6.5) agus python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Teastaíonn uait ar a laghad Tor 0.2.7.1 agus python3-stem 1.4.0 chun OnionShare a úsáid.", + "gui_settings_window_title": "Socruithe", + "gui_settings_whats_this": "Cad é seo", + "gui_settings_stealth_option": "Úsáid údarú cliaint (seanleagan)", + "gui_settings_stealth_hidservauth_string": "Toisc gur shábháil tú d'eochair phríobháideach, anois is féidir leat\ncliceáil chun an HidServAuth a chóipeáil.", + "gui_settings_autoupdate_label": "Lorg nuashonruithe", + "gui_settings_autoupdate_option": "Cuir in iúl dom nuair a bheidh leagan nua ar fáil", + "gui_settings_autoupdate_timestamp": "Seiceáilte: {}", + "gui_settings_autoupdate_timestamp_never": "Níor seiceáladh riamh", + "gui_settings_autoupdate_check_button": "Lorg Nuashonrú", + "gui_settings_general_label": "Socruithe ginearálta", + "gui_settings_sharing_label": "Socruithe comhroinnte", + "gui_settings_close_after_first_download_option": "Stop ag comhroinnt tar éis an chéad íoslódáil", + "gui_settings_connection_type_label": "Cén chaoi ar chóir do OnionShare ceangal le Tor?", + "gui_settings_connection_type_bundled_option": "Úsáid an leagan de Tor ionsuite in OnionShare", + "gui_settings_connection_type_automatic_option": "Déan cumraíocht uathoibríoch le Brabhsálaí Tor", + "gui_settings_connection_type_control_port_option": "Ceangal trí phort rialaithe", + "gui_settings_connection_type_socket_file_option": "Ceangal trí chomhad soicéid", + "gui_settings_connection_type_test_button": "Tástáil an Ceangal le Tor", + "gui_settings_control_port_label": "Port rialaithe", + "gui_settings_socket_file_label": "Comhad soicéid", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Socruithe fíordheimhnithe Tor", + "gui_settings_authenticate_no_auth_option": "Gan fíordheimhniú, nó fíordheimhniú le fianán", + "gui_settings_authenticate_password_option": "Focal faire", + "gui_settings_password_label": "Focal faire", + "gui_settings_tor_bridges": "Tacaíocht do dhroichid Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ná húsáid droichid", + "gui_settings_tor_bridges_obfs4_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4 (obfs4proxy de dhíth)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite(Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite (Azure) (obfs4proxy de dhíth)", + "gui_settings_meek_lite_expensive_warning": "Rabhadh: Tá sé an-chostasach ar Thionscadal Tor na droichid meek_lite a chur ar fáil.

    Iarraimid ort gan iad a úsáid má tá tú in ann ceangal díreach a bhunú le Tor, nó trí chóras iompair obfs4, nó trí dhroichead eile.", + "gui_settings_tor_bridges_custom_radio_option": "Úsáid droichid shaincheaptha", + "gui_settings_tor_bridges_custom_label": "Is féidir leat droichid a fháil ó https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Níl aon cheann de na droichid ag obair.\nSeiceáil arís iad, nó bain triail as droichid eile.", + "gui_settings_button_save": "Sábháil", + "gui_settings_button_cancel": "Cealaigh", + "gui_settings_button_help": "Cabhair", + "gui_settings_shutdown_timeout_checkbox": "Úsáid amadóir uathstoptha", + "gui_settings_shutdown_timeout": "Stop ag comhroinnt ag:", + "settings_error_unknown": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor toisc nach féidir linn ciall a bhaint as na socruithe.", + "settings_error_automatic": "Níorbh fhéidir ceangal a bhunú leis an rialaitheoir Tor. An bhfuil Brabhsálaí Tor (ar fáil ó torproject.org) ag rith sa gcúlra?", + "settings_error_socket_port": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor ag {}:{}.", + "settings_error_socket_file": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor trí chomhad soicéid {}.", + "settings_error_auth": "Ceangailte le {}:{}, ach ní féidir an ceangal a fhíordheimhniú. B'fhéidir nach rialaitheoir Tor é seo?", + "settings_error_missing_password": "Ceangailte le rialaitheoir Tor, ach teastaíonn focal faire uaidh.", + "settings_error_unreadable_cookie_file": "Ceangailte le rialaitheoir Tor, ach seans go bhfuil an focal faire mícheart, nó níl cead ag an úsáideoir an comhad ina bhfuil na fianáin a léamh.", + "settings_error_bundled_tor_not_supported": "Ní féidir an leagan de Tor a thagann le OnionShare a úsáid sa mód forbartha ar Windows nó ar macOS.", + "settings_error_bundled_tor_timeout": "An iomarca ama ag ceangal le Tor. B'fhéidir nach bhfuil ceangailte leis an Idirlíon, nó nach bhfuil clog do chórais socraithe mar is ceart?", + "settings_error_bundled_tor_broken": "Níorbh fhéidir le OnionShare ceangal le Tor sa gcúlra:\n{}", + "settings_test_success": "Ceangailte leis an rialaitheoir Tor.\n\nLeagan de Tor: {}\nTacaíonn sé le seirbhísí onion gearrshaolacha: {}.\nTacaíonn sé le fíordheimhniú cliaint: {}.\nTacaíonn sé le seoltaí .onion den chéad ghlúin eile: {}.", + "error_tor_protocol_error": "Tharla earráid le Tor: {}", + "error_tor_protocol_error_unknown": "Tharla earráid anaithnid le Tor", + "error_invalid_private_key": "Ní thacaítear le heochair phríobháideach den sórt seo", + "connecting_to_tor": "Ag ceangal le líonra Tor", + "update_available": "Leagan nua de OnionShare ar fáil. Cliceáil anseo lena íoslódáil.

    Tá {} agat agus is é {} an leagan is déanaí.", + "update_error_check_error": "Theip orainn nuashonruithe a lorg: Deir suíomh Gréasáin OnionShare gurb é '{}' an leagan is déanaí, leagan nach n-aithnímid…", + "update_error_invalid_latest_version": "Theip orainn nuashonruithe a lorg: B'fhéidir nach bhfuil ceangailte le Tor, nó nach bhfuil suíomh OnionShare ag obair faoi láthair?", + "update_not_available": "Tá an leagan is déanaí de OnionShare agat cheana.", + "gui_tor_connection_ask": "An bhfuil fonn ort na socruithe líonra a oscailt chun an fhadhb a réiteach?", + "gui_tor_connection_ask_open_settings": "Tá", + "gui_tor_connection_ask_quit": "Scoir", + "gui_tor_connection_error_settings": "Bain triail as na socruithe líonra a athrú chun ceangal le líonra Tor ó OnionShare.", + "gui_tor_connection_canceled": "Níorbh fhéidir ceangal a bhunú le Tor.\n\nDeimhnigh go bhfuil tú ceangailte leis an Idirlíon, ansin oscail OnionShare arís agus socraigh an ceangal le Tor.", + "gui_tor_connection_lost": "Dícheangailte ó Tor.", + "gui_server_started_after_timeout": "Bhí an t-amadóir uathstoptha caite sular thosaigh an freastalaí.\nCaithfidh tú comhroinnt nua a chruthú.", + "gui_server_timeout_expired": "Tá an t-amadóir uathstoptha caite cheana.\nCaithfidh tú é a athshocrú sular féidir leat comhaid a chomhroinnt.", + "share_via_onionshare": "Comhroinn trí OnionShare é", + "gui_use_legacy_v2_onions_checkbox": "Úsáid seoltaí sean-nóis", + "gui_save_private_key_checkbox": "Úsáid seoladh seasmhach (seanleagan)", + "gui_share_url_description": "Tá aon duine a bhfuil an seoladh OnionShare aige/aici in ann do chuid comhad a íoslódáil le Brabhsálaí Tor: ", + "gui_receive_url_description": "Tá aon duine a bhfuil an seoladh OnionShare aige/aici in ann comhaid a uaslódáil go dtí do ríomhaire le Brabhsálaí Tor: ", + "gui_url_label_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.

    Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)", + "gui_url_label_stay_open": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.", + "gui_url_label_onetime": "Stopfaidh an chomhroinnt seo nuair a chríochnóidh sé den chéad uair.", + "gui_url_label_onetime_and_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.

    Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)", + "gui_status_indicator_share_stopped": "Réidh le comhroinnt", + "gui_status_indicator_share_working": "Á thosú…", + "gui_status_indicator_share_started": "Comhroinnt", + "gui_status_indicator_receive_stopped": "Réidh le glacadh le comhaid", + "gui_status_indicator_receive_working": "Á thosú…", + "gui_status_indicator_receive_started": "Glacadh", + "gui_file_info": "{} comhad, {}", + "gui_file_info_single": "{} chomhad, {}", + "history_in_progress_tooltip": "{} ar siúl", + "history_completed_tooltip": "{} críochnaithe", + "info_in_progress_uploads_tooltip": "{} uaslódáil ar siúl faoi láthair", + "info_completed_uploads_tooltip": "{} uaslódáil críochnaithe", + "error_cannot_create_downloads_dir": "Níorbh fhéidir fillteán a chruthú do chomhaid a nglacann tú leo: {}", + "receive_mode_downloads_dir": "Cuirfear comhaid a sheoltar chugat san fhillteán seo: {}", + "receive_mode_warning": "Rabhadh: Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire, fiú comhaid chontúirteacha a dhéanfadh dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.", + "gui_receive_mode_warning": "Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire.

    Tá comhaid áirithe an-chontúirteach agus dhéanfaidís dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.", + "receive_mode_upload_starting": "Uaslódáil, méid iomlán {}, á tosú", + "receive_mode_received_file": "Faighte: {}", + "gui_mode_share_button": "Comhroinn Comhaid", + "gui_mode_receive_button": "Glac le Comhaid", + "gui_settings_receiving_label": "Socruithe glactha", + "gui_settings_downloads_label": "Sábháil comhaid i", + "gui_settings_downloads_button": "Brabhsáil", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Tá cead ag an seoltóir stop a chur leis an mód glactha", + "gui_settings_public_mode_checkbox": "Mód poiblí", + "systray_close_server_title": "Tá an freastalaí OnionShare dúnta", + "systray_close_server_message": "Dhún úsáideoir an freastalaí", + "systray_page_loaded_title": "Lódáladh leathanach OnionShare", + "systray_download_page_loaded_message": "Lódáil úsáideoir an leathanach íoslódála", + "systray_upload_page_loaded_message": "Lódáil úsáideoir an leathanach uaslódála", + "gui_uploads": "Stair Uaslódála", + "gui_no_uploads": "Níl aon rud uaslódáilte agat fós", + "gui_clear_history": "Glan Uile", + "gui_upload_in_progress": "Tosaíodh an Uaslódáil {}", + "gui_upload_finished_range": "Uaslódáladh {} go {}", + "gui_upload_finished": "Uaslódáladh {}", + "gui_download_in_progress": "Tosaíodh an Íoslódáil {}", + "gui_open_folder_error_nautilus": "Ní féidir an fillteán a oscailt toisc nach bhfuil nautilus ar fáil. Tá an comhad anseo: {}", + "gui_settings_language_label": "Do rogha teanga", + "gui_settings_language_changed_notice": "Atosaigh OnionShare chun an teanga nua a chur i bhfeidhm." } -- cgit v1.2.3-54-g00ecf From aadb2a01f0ced1f31268adb6237f9ddd0bbc849a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 5 Dec 2018 20:14:52 -0800 Subject: Only show onion settings if there is a Tor connection --- onionshare/common.py | 5 +++++ onionshare_gui/settings_dialog.py | 32 ++++++++++++++++++++++++++------ share/locale/en.json | 1 + 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index ffa6529f..250972f9 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -373,6 +373,11 @@ class Common(object): 'settings_whats_this': """ QLabel { font-size: 12px; + }""", + + 'settings_connect_to_tor': """ + QLabel { + font-style: italic; }""" } diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 958d49fd..37cc9105 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -88,6 +88,10 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_widget = QtWidgets.QWidget() self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + # Label telling user to connect to Tor for onion service settings + connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) + connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) @@ -149,16 +153,32 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) self.hidservauth_copy_button.hide() + # Onion settings widget + onion_settings_layout = QtWidgets.QVBoxLayout() + onion_settings_layout.setContentsMargins(0, 0, 0, 0) + onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget) + onion_settings_layout.addWidget(self.save_private_key_widget) + onion_settings_layout.addWidget(self.use_stealth_widget) + onion_settings_layout.addWidget(hidservauth_details) + onion_settings_layout.addWidget(self.hidservauth_copy_button) + onion_settings_widget = QtWidgets.QWidget() + onion_settings_widget.setLayout(onion_settings_layout) + + # If we're connected to Tor, show onion service settings, show label if not + if self.onion.is_authenticated(): + connect_to_tor_label.hide() + onion_settings_widget.show() + else: + connect_to_tor_label.show() + onion_settings_widget.hide() + + # General options layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.public_mode_widget) general_group_layout.addWidget(self.shutdown_timeout_widget) - general_group_layout.addWidget(self.use_legacy_v2_onions_widget) - general_group_layout.addWidget(self.save_private_key_widget) - general_group_layout.addWidget(self.use_stealth_widget) - general_group_layout.addWidget(hidservauth_details) - general_group_layout.addWidget(self.hidservauth_copy_button) - + general_group_layout.addWidget(connect_to_tor_label) + general_group_layout.addWidget(onion_settings_widget) general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) general_group.setLayout(general_group_layout) diff --git a/share/locale/en.json b/share/locale/en.json index 7fb30df8..c38bfdff 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -135,6 +135,7 @@ "gui_server_started_after_timeout": "The auto-stop timer ran out before the server started.\nPlease make a new share.", "gui_server_timeout_expired": "The auto-stop timer already ran out.\nPlease update it to start sharing.", "share_via_onionshare": "OnionShare it", + "gui_connect_to_tor_for_onion_settings": "Connect to Tor to see onion service settings", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", "gui_save_private_key_checkbox": "Use a persistent address (legacy)", "gui_share_url_description": "Anyone with this OnionShare address can download your files using the Tor Browser: ", -- cgit v1.2.3-54-g00ecf From 1d1efb7e54bcc6dbfc56703a257f1de9f1ca3265 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 5 Dec 2018 20:33:45 -0800 Subject: Require tor 0.4.0.0 for v3 onion services (will change in the future). And update settings dialog so if the connected version of tor doesn't support v3 onions, then always show legacy options. If it does support v3 onions, allow 'Use legacy addresses' --- onionshare/onion.py | 5 ++++- onionshare_gui/settings_dialog.py | 31 +++++++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 6066f059..6673f8ee 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -387,6 +387,7 @@ class Onion(object): # Get the tor version self.tor_version = self.c.get_version().version_str + self.common.log('Onion', 'connect', 'Connected to tor {}'.format(self.tor_version)) # Do the versions of stem and tor that I'm using support ephemeral onion services? list_ephemeral_hidden_services = getattr(self.c, "list_ephemeral_hidden_services", None) @@ -403,7 +404,9 @@ class Onion(object): self.supports_stealth = False # Does this version of Tor support next-gen ('v3') onions? - self.supports_next_gen_onions = self.tor_version > Version('0.3.3.1') + # Note, this is the version of Tor where this bug was fixed: + # https://trac.torproject.org/projects/tor/ticket/28619 + self.supports_v3_onions = self.tor_version > Version('0.4.0.0') def is_authenticated(self): """ diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 37cc9105..53fd8351 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -164,15 +164,6 @@ class SettingsDialog(QtWidgets.QDialog): onion_settings_widget = QtWidgets.QWidget() onion_settings_widget.setLayout(onion_settings_layout) - # If we're connected to Tor, show onion service settings, show label if not - if self.onion.is_authenticated(): - connect_to_tor_label.hide() - onion_settings_widget.show() - else: - connect_to_tor_label.show() - onion_settings_widget.hide() - - # General options layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.public_mode_widget) @@ -480,6 +471,7 @@ class SettingsDialog(QtWidgets.QDialog): self.setLayout(layout) self.cancel_button.setFocus() + # Load settings, and fill them in self.old_settings = Settings(self.common, self.config) self.old_settings.load() @@ -599,6 +591,25 @@ class SettingsDialog(QtWidgets.QDialog): new_bridges = ''.join(new_bridges) self.tor_bridges_use_custom_textbox.setPlainText(new_bridges) + # If we're connected to Tor, show onion service settings, show label if not + if self.onion.is_authenticated(): + connect_to_tor_label.hide() + onion_settings_widget.show() + + # If v3 onion services are supported, allow using legacy mode + if self.onion.supports_v3_onions: + self.common.log('SettingsDialog', '__init__', 'v3 onions are supported') + self.use_legacy_v2_onions_checkbox.show() + else: + self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported') + self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_legacy_v2_onions_widget.hide() + self.use_legacy_v2_onions_checkbox_clicked(True) + else: + connect_to_tor_label.show() + onion_settings_widget.hide() + + def connection_type_bundled_toggled(self, checked): """ Connection type bundled was toggled. If checked, hide authentication fields. @@ -774,7 +785,7 @@ class SettingsDialog(QtWidgets.QDialog): 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 - Alert(self.common, strings._('settings_test_success').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_v3_onions)) # Clean up onion.cleanup() -- cgit v1.2.3-54-g00ecf From 657806a003a2fc2fb074f149ac8802ef99ad090e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 5 Dec 2018 20:46:01 -0800 Subject: Only allow starting v3 onion services if the tor that we're connected to supports it --- onionshare/onion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 6673f8ee..3aeffe91 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -406,7 +406,7 @@ class Onion(object): # Does this version of Tor support next-gen ('v3') onions? # Note, this is the version of Tor where this bug was fixed: # https://trac.torproject.org/projects/tor/ticket/28619 - self.supports_v3_onions = self.tor_version > Version('0.4.0.0') + self.supports_v3_onions = self.tor_version >= Version('0.4.0.0') def is_authenticated(self): """ @@ -464,7 +464,7 @@ class Onion(object): else: key_type = "NEW" # Work out if we can support v3 onion services, which are preferred - if Version(self.tor_version) >= Version('0.3.3.1') and not self.settings.get('use_legacy_v2_onions'): + if self.supports_v3_onions and not self.settings.get('use_legacy_v2_onions'): key_content = "ED25519-V3" else: # fall back to v2 onion services -- cgit v1.2.3-54-g00ecf From 16e301af8d42c7a911539ed0945db2c6918063de Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 5 Dec 2018 20:53:03 -0800 Subject: Don't actually check the 'Use legacy addresses' checkbox when it's hidden --- onionshare_gui/settings_dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 53fd8351..1ff20b31 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -602,7 +602,6 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.show() else: self.common.log('SettingsDialog', '__init__', 'v3 onions are not supported') - self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_widget.hide() self.use_legacy_v2_onions_checkbox_clicked(True) else: -- cgit v1.2.3-54-g00ecf From 300434e5ec452a5c622419a929694f698a83414c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 5 Dec 2018 23:05:25 -0800 Subject: Update settings dialog tests to use an OnionStub instead of an Onion, and test different states of tor (authenticate and not, supports v3 and not) --- onionshare/onion.py | 3 + onionshare_gui/settings_dialog.py | 22 ++- tests/SettingsGuiBaseTest.py | 202 ++++++++++++++++++++- ...l_onionshare_settings_dialog_legacy_tor_test.py | 27 +++ ...local_onionshare_settings_dialog_no_tor_test.py | 27 +++ tests/local_onionshare_settings_dialog_test.py | 172 ------------------ ...local_onionshare_settings_dialog_v3_tor_test.py | 26 +++ 7 files changed, 295 insertions(+), 184 deletions(-) create mode 100644 tests/local_onionshare_settings_dialog_legacy_tor_test.py create mode 100644 tests/local_onionshare_settings_dialog_no_tor_test.py delete mode 100644 tests/local_onionshare_settings_dialog_test.py create mode 100644 tests/local_onionshare_settings_dialog_v3_tor_test.py diff --git a/onionshare/onion.py b/onionshare/onion.py index 3aeffe91..3d7b4514 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -146,6 +146,9 @@ class Onion(object): # The tor process self.tor_proc = None + # The Tor controller + self.c = None + # Start out not connected to Tor self.connected_to_tor = False diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 1ff20b31..92c84262 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -89,8 +89,8 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout) # Label telling user to connect to Tor for onion service settings - connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) - connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) + self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() @@ -161,15 +161,15 @@ class SettingsDialog(QtWidgets.QDialog): onion_settings_layout.addWidget(self.use_stealth_widget) onion_settings_layout.addWidget(hidservauth_details) onion_settings_layout.addWidget(self.hidservauth_copy_button) - onion_settings_widget = QtWidgets.QWidget() - onion_settings_widget.setLayout(onion_settings_layout) + self.onion_settings_widget = QtWidgets.QWidget() + self.onion_settings_widget.setLayout(onion_settings_layout) # General options layout general_group_layout = QtWidgets.QVBoxLayout() general_group_layout.addWidget(self.public_mode_widget) general_group_layout.addWidget(self.shutdown_timeout_widget) - general_group_layout.addWidget(connect_to_tor_label) - general_group_layout.addWidget(onion_settings_widget) + general_group_layout.addWidget(self.connect_to_tor_label) + general_group_layout.addWidget(self.onion_settings_widget) general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) general_group.setLayout(general_group_layout) @@ -471,7 +471,9 @@ class SettingsDialog(QtWidgets.QDialog): self.setLayout(layout) self.cancel_button.setFocus() + self.reload_settings() + def reload_settings(self): # Load settings, and fill them in self.old_settings = Settings(self.common, self.config) self.old_settings.load() @@ -593,8 +595,8 @@ class SettingsDialog(QtWidgets.QDialog): # If we're connected to Tor, show onion service settings, show label if not if self.onion.is_authenticated(): - connect_to_tor_label.hide() - onion_settings_widget.show() + self.connect_to_tor_label.hide() + self.onion_settings_widget.show() # If v3 onion services are supported, allow using legacy mode if self.onion.supports_v3_onions: @@ -605,8 +607,8 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_widget.hide() self.use_legacy_v2_onions_checkbox_clicked(True) else: - connect_to_tor_label.show() - onion_settings_widget.hide() + self.connect_to_tor_label.show() + self.onion_settings_widget.hide() def connection_type_bundled_toggled(self, checked): diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index e59a58a8..57626d1d 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -1,5 +1,7 @@ import json import os +import unittest +from PyQt5 import QtCore, QtTest from onionshare import strings from onionshare.common import Common @@ -8,10 +10,27 @@ from onionshare.onion import Onion from onionshare_gui import Application, OnionShare from onionshare_gui.settings_dialog import SettingsDialog + +class OnionStub(object): + def __init__(self, is_authenticated, supports_v3_onions=False): + self._is_authenticated = is_authenticated + self.supports_v3_onions = True + + def is_authenticated(self): + return self._is_authenticated + + class SettingsGuiBaseTest(object): @staticmethod - def set_up(test_settings): + def set_up(): '''Create the GUI''' + + # Default settings for the settings GUI tests + test_settings = { + "no_bridges": False, + "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", + } + # Create our test file testfile = open('/tmp/test.txt', 'w') testfile.write('onionshare') @@ -37,8 +56,187 @@ class SettingsGuiBaseTest(object): gui = SettingsDialog(common, testonion, qtapp, '/tmp/settings.json', True) return gui - @staticmethod def tear_down(): '''Clean up after tests''' os.remove('/tmp/settings.json') + + def run_settings_gui_tests(self): + self.gui.show() + self.gui.qtapp.processEvents() + + # Window is shown + self.assertTrue(self.gui.isVisible()) + self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title')) + + # Check for updates button is hidden + self.assertFalse(self.gui.check_for_updates_button.isVisible()) + + # public mode is off + self.assertFalse(self.gui.public_mode_checkbox.isChecked()) + # enable public mode + QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2)) + self.assertTrue(self.gui.public_mode_checkbox.isChecked()) + + # shutdown timer is off + self.assertFalse(self.gui.shutdown_timeout_checkbox.isChecked()) + # enable shutdown timer + QtTest.QTest.mouseClick(self.gui.shutdown_timeout_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.shutdown_timeout_checkbox.height()/2)) + self.assertTrue(self.gui.shutdown_timeout_checkbox.isChecked()) + + # legacy mode checkbox and related widgets + if self.gui.onion.is_authenticated(): + if self.gui.onion.supports_v3_onions: + # legacy mode is off + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked()) + # persistence, stealth is hidden and disabled + self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertFalse(self.gui.save_private_key_checkbox.isChecked()) + self.assertFalse(self.gui.use_stealth_widget.isVisible()) + self.assertFalse(self.gui.stealth_checkbox.isChecked()) + self.assertFalse(self.gui.hidservauth_copy_button.isVisible()) + + # enable legacy mode + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked()) + self.assertTrue(self.gui.save_private_key_checkbox.isVisible()) + self.assertTrue(self.gui.use_stealth_widget.isVisible()) + + # enable persistent mode + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + self.assertTrue(self.gui.save_private_key_checkbox.isChecked()) + # enable stealth mode + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + self.assertTrue(self.gui.stealth_checkbox.isChecked()) + # now that stealth, persistence are enabled, we can't turn off legacy mode + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) + # disable stealth, persistence + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + # legacy mode checkbox is enabled again + self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) + # uncheck legacy mode + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + # legacy options hidden again + self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertFalse(self.gui.use_stealth_widget.isVisible()) + + # re-enable legacy mode + QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) + + else: + # legacy mode setting is hidden + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible()) + # legacy options are showing + self.assertTrue(self.gui.save_private_key_widget.isVisible()) + self.assertTrue(self.gui.use_stealth_widget.isVisible()) + + # enable them all again so that we can see the setting stick in settings.json + QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) + QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) + else: + # None of the onion settings should appear + self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isVisible()) + self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertFalse(self.gui.save_private_key_checkbox.isChecked()) + self.assertFalse(self.gui.use_stealth_widget.isVisible()) + self.assertFalse(self.gui.stealth_checkbox.isChecked()) + self.assertFalse(self.gui.hidservauth_copy_button.isVisible()) + + # stay open toggled off, on + self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked()) + QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2)) + self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked()) + + # receive mode + self.gui.downloads_dir_lineedit.setText('/tmp/OnionShareSettingsTest') + + + # bundled mode is enabled + self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_bundled_radio.isChecked()) + # bridge options are shown + self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible()) + # bridges are set to custom + self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked()) + self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) + + # switch to obfs4 + QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2)) + self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked()) + + # custom bridges are hidden + self.assertFalse(self.gui.tor_bridges_use_custom_textbox_options.isVisible()) + # other modes are unchecked but enabled + self.assertTrue(self.gui.connection_type_automatic_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_control_port_radio.isEnabled()) + self.assertTrue(self.gui.connection_type_socket_file_radio.isEnabled()) + self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) + self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) + self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked()) + + # enable automatic mode + QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2)) + self.assertTrue(self.gui.connection_type_automatic_radio.isChecked()) + # bundled is off + self.assertFalse(self.gui.connection_type_bundled_radio.isChecked()) + # bridges are hidden + self.assertFalse(self.gui.connection_type_bridges_radio_group.isVisible()) + + # auth type is hidden in bundled or automatic mode + self.assertFalse(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertFalse(self.gui.authenticate_password_radio.isVisible()) + + # enable control port mode + QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2)) + self.assertTrue(self.gui.connection_type_control_port_radio.isChecked()) + # automatic is off + self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) + # auth options appear + self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertTrue(self.gui.authenticate_password_radio.isVisible()) + + # enable socket mode + QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2)) + self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked()) + # control port is off + self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) + # auth options are still present + self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) + self.assertTrue(self.gui.authenticate_password_radio.isVisible()) + + # re-enable bundled mode + QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2)) + # go back to custom bridges + QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2)) + self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) + self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible()) + self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked()) + self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3') + + # Test that the Settings Dialog can save the settings and close itself + QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.isVisible()) + + # Test our settings are reflected in the settings json + with open('/tmp/settings.json') as f: + data = json.load(f) + + self.assertTrue(data["public_mode"]) + self.assertTrue(data["shutdown_timeout"]) + + if self.gui.onion.is_authenticated(): + if self.gui.onion.supports_v3_onions: + self.assertTrue(data["use_legacy_v2_onions"]) + self.assertTrue(data["save_private_key"]) + self.assertTrue(data["use_stealth"]) + else: + self.assertFalse(data["use_legacy_v2_onions"]) + self.assertFalse(data["save_private_key"]) + self.assertFalse(data["use_stealth"]) + + self.assertEqual(data["downloads_dir"], "/tmp/OnionShareSettingsTest") + self.assertFalse(data["close_after_first_download"]) + self.assertEqual(data["connection_type"], "bundled") + self.assertFalse(data["tor_bridges_use_obfs4"]) + self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n") diff --git a/tests/local_onionshare_settings_dialog_legacy_tor_test.py b/tests/local_onionshare_settings_dialog_legacy_tor_test.py new file mode 100644 index 00000000..d08362f1 --- /dev/null +++ b/tests/local_onionshare_settings_dialog_legacy_tor_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import json +import unittest +import time +from PyQt5 import QtCore, QtTest + +from onionshare import strings +from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub + + +class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): + @classmethod + def setUpClass(cls): + cls.gui = SettingsGuiBaseTest.set_up() + + @classmethod + def tearDownClass(cls): + SettingsGuiBaseTest.tear_down() + + def test_gui_legacy_tor(self): + self.gui.onion = OnionStub(True, False) + self.gui.reload_settings() + self.run_settings_gui_tests() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py new file mode 100644 index 00000000..a10c2f2e --- /dev/null +++ b/tests/local_onionshare_settings_dialog_no_tor_test.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import json +import unittest +import time +from PyQt5 import QtCore, QtTest + +from onionshare import strings +from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub + + +class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): + @classmethod + def setUpClass(cls): + cls.gui = SettingsGuiBaseTest.set_up() + + @classmethod + def tearDownClass(cls): + SettingsGuiBaseTest.tear_down() + + def test_gui_no_tor(self): + self.gui.onion = OnionStub(False) + self.gui.reload_settings() + self.run_settings_gui_tests() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/local_onionshare_settings_dialog_test.py b/tests/local_onionshare_settings_dialog_test.py deleted file mode 100644 index c1e48122..00000000 --- a/tests/local_onionshare_settings_dialog_test.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python3 -import json -import unittest -from PyQt5 import QtCore, QtTest - -from onionshare import strings -from .SettingsGuiBaseTest import SettingsGuiBaseTest - -class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): - @classmethod - def setUpClass(cls): - test_settings = { - "no_bridges": False, - "tor_bridges_use_custom_bridges": "Bridge 1.2.3.4:56 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 5.6.7.8:910 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\nBridge 11.12.13.14:1516 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n", - } - cls.gui = SettingsGuiBaseTest.set_up(test_settings) - - @classmethod - def tearDownClass(cls): - SettingsGuiBaseTest.tear_down() - - def test_gui(self): - self.gui.show() - # Window is shown - self.assertTrue(self.gui.isVisible()) - self.assertEqual(self.gui.windowTitle(), strings._('gui_settings_window_title')) - # Check for updates button is hidden - self.assertFalse(self.gui.check_for_updates_button.isVisible()) - - # public mode is off - self.assertFalse(self.gui.public_mode_checkbox.isChecked()) - # enable public mode - QtTest.QTest.mouseClick(self.gui.public_mode_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.public_mode_checkbox.height()/2)) - self.assertTrue(self.gui.public_mode_checkbox.isChecked()) - - # shutdown timer is off - self.assertFalse(self.gui.shutdown_timeout_checkbox.isChecked()) - # enable shutdown timer - QtTest.QTest.mouseClick(self.gui.shutdown_timeout_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.shutdown_timeout_checkbox.height()/2)) - self.assertTrue(self.gui.shutdown_timeout_checkbox.isChecked()) - - # legacy mode checkbox and related widgets - # legacy mode is off - self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked()) - # persistence, stealth is hidden and disabled - self.assertFalse(self.gui.save_private_key_widget.isVisible()) - self.assertFalse(self.gui.save_private_key_checkbox.isChecked()) - self.assertFalse(self.gui.use_stealth_widget.isVisible()) - self.assertFalse(self.gui.stealth_checkbox.isChecked()) - self.assertFalse(self.gui.hidservauth_copy_button.isVisible()) - - # enable legacy mode - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) - self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isChecked()) - self.assertTrue(self.gui.save_private_key_checkbox.isVisible()) - self.assertTrue(self.gui.use_stealth_widget.isVisible()) - # enable persistent mode - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) - self.assertTrue(self.gui.save_private_key_checkbox.isChecked()) - # enable stealth mode - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) - self.assertTrue(self.gui.stealth_checkbox.isChecked()) - # now that stealth, persistence are enabled, we can't turn off legacy mode - self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) - # disable stealth, persistence - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) - # legacy mode checkbox is enabled again - self.assertTrue(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) - # uncheck legacy mode - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) - # legacy options hidden again - self.assertFalse(self.gui.save_private_key_widget.isVisible()) - self.assertFalse(self.gui.use_stealth_widget.isVisible()) - # enable them all again so that we can see the setting stick in settings.json - QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) - QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) - QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) - - - # stay open toggled off, on - self.assertTrue(self.gui.close_after_first_download_checkbox.isChecked()) - QtTest.QTest.mouseClick(self.gui.close_after_first_download_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.close_after_first_download_checkbox.height()/2)) - self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked()) - - # receive mode - self.gui.downloads_dir_lineedit.setText('/tmp/OnionShareSettingsTest') - - - # bundled mode is enabled - self.assertTrue(self.gui.connection_type_bundled_radio.isEnabled()) - self.assertTrue(self.gui.connection_type_bundled_radio.isChecked()) - # bridge options are shown - self.assertTrue(self.gui.connection_type_bridges_radio_group.isVisible()) - # bridges are set to custom - self.assertFalse(self.gui.tor_bridges_no_bridges_radio.isChecked()) - self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) - - # switch to obfs4 - QtTest.QTest.mouseClick(self.gui.tor_bridges_use_obfs4_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_obfs4_radio.height()/2)) - self.assertTrue(self.gui.tor_bridges_use_obfs4_radio.isChecked()) - - # custom bridges are hidden - self.assertFalse(self.gui.tor_bridges_use_custom_textbox_options.isVisible()) - # other modes are unchecked but enabled - self.assertTrue(self.gui.connection_type_automatic_radio.isEnabled()) - self.assertTrue(self.gui.connection_type_control_port_radio.isEnabled()) - self.assertTrue(self.gui.connection_type_socket_file_radio.isEnabled()) - self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) - self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) - self.assertFalse(self.gui.connection_type_socket_file_radio.isChecked()) - - # enable automatic mode - QtTest.QTest.mouseClick(self.gui.connection_type_automatic_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_automatic_radio.height()/2)) - self.assertTrue(self.gui.connection_type_automatic_radio.isChecked()) - # bundled is off - self.assertFalse(self.gui.connection_type_bundled_radio.isChecked()) - # bridges are hidden - self.assertFalse(self.gui.connection_type_bridges_radio_group.isVisible()) - - # auth type is hidden in bundled or automatic mode - self.assertFalse(self.gui.authenticate_no_auth_radio.isVisible()) - self.assertFalse(self.gui.authenticate_password_radio.isVisible()) - - # enable control port mode - QtTest.QTest.mouseClick(self.gui.connection_type_control_port_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_control_port_radio.height()/2)) - self.assertTrue(self.gui.connection_type_control_port_radio.isChecked()) - # automatic is off - self.assertFalse(self.gui.connection_type_automatic_radio.isChecked()) - # auth options appear - self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) - self.assertTrue(self.gui.authenticate_password_radio.isVisible()) - - # enable socket mode - QtTest.QTest.mouseClick(self.gui.connection_type_socket_file_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_socket_file_radio.height()/2)) - self.assertTrue(self.gui.connection_type_socket_file_radio.isChecked()) - # control port is off - self.assertFalse(self.gui.connection_type_control_port_radio.isChecked()) - # auth options are still present - self.assertTrue(self.gui.authenticate_no_auth_radio.isVisible()) - self.assertTrue(self.gui.authenticate_password_radio.isVisible()) - - # re-enable bundled mode - QtTest.QTest.mouseClick(self.gui.connection_type_bundled_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.connection_type_bundled_radio.height()/2)) - # go back to custom bridges - QtTest.QTest.mouseClick(self.gui.tor_bridges_use_custom_radio, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.tor_bridges_use_custom_radio.height()/2)) - self.assertTrue(self.gui.tor_bridges_use_custom_radio.isChecked()) - self.assertTrue(self.gui.tor_bridges_use_custom_textbox.isVisible()) - self.assertFalse(self.gui.tor_bridges_use_obfs4_radio.isChecked()) - self.gui.tor_bridges_use_custom_textbox.setPlainText('94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\n148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\n93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3') - - # Test that the Settings Dialog can save the settings and close itself - QtTest.QTest.mouseClick(self.gui.save_button, QtCore.Qt.LeftButton) - self.assertFalse(self.gui.isVisible()) - - # Test our settings are reflected in the settings json - with open('/tmp/settings.json') as f: - data = json.load(f) - - self.assertTrue(data["public_mode"]) - self.assertTrue(data["shutdown_timeout"]) - self.assertTrue(data["use_legacy_v2_onions"]) - self.assertTrue(data["save_private_key"]) - self.assertTrue(data["use_stealth"]) - self.assertEqual(data["downloads_dir"], "/tmp/OnionShareSettingsTest") - self.assertFalse(data["close_after_first_download"]) - self.assertEqual(data["connection_type"], "bundled") - self.assertFalse(data["tor_bridges_use_obfs4"]) - self.assertEqual(data["tor_bridges_use_custom_bridges"], "Bridge 94.242.249.2:83 E25A95F1DADB739F0A83EB0223A37C02FD519306\nBridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC\nBridge 93.80.47.217:41727 A6A0D497D98097FCFE91D639548EE9E34C15CDD3\n") - -if __name__ == "__main__": - unittest.main() diff --git a/tests/local_onionshare_settings_dialog_v3_tor_test.py b/tests/local_onionshare_settings_dialog_v3_tor_test.py new file mode 100644 index 00000000..815b2f72 --- /dev/null +++ b/tests/local_onionshare_settings_dialog_v3_tor_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import json +import unittest +from PyQt5 import QtCore, QtTest + +from onionshare import strings +from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub + + +class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): + @classmethod + def setUpClass(cls): + cls.gui = SettingsGuiBaseTest.set_up() + + @classmethod + def tearDownClass(cls): + SettingsGuiBaseTest.tear_down() + + def test_gui_v3_tor(self): + self.gui.onion = OnionStub(True, True) + self.gui.reload_settings() + self.run_settings_gui_tests() + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3-54-g00ecf From 9869c679f0eb8ed09dbfdf60469391a1117b750a Mon Sep 17 00:00:00 2001 From: fadelkon Date: Thu, 6 Dec 2018 12:43:43 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 227 ++++++++++++++++++++++++++------------------------- 1 file changed, 114 insertions(+), 113 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 0e95a29c..40b6bac2 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -1,121 +1,121 @@ { - "config_onion_service": "", - "preparing_files": "Comprimint arxius.", - "give_this_url": "Dona aquesta adreça al destinatari:", - "give_this_url_stealth": "", - "give_this_url_receive": "Dona aquesta adreça al remitent:", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", + "config_onion_service": "S'està establint el servei onion al port {0:d}.", + "preparing_files": "S'estan comprimint els arxius.", + "give_this_url": "Dóna aquesta adreça a la persona destinatària:", + "give_this_url_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona destinatària:", + "give_this_url_receive": "Dóna aquesta adreça a la persona remitent:", + "give_this_url_receive_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona remitent:", + "ctrlc_to_stop": "Prem Control+C per aturar el servidor", "not_a_file": "{0:s} no és un arxiu vàlid.", "not_a_readable_file": "{0:s} no és un arxiu llegible.", - "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei de ceba", + "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei onion", "other_page_loaded": "Adreça carregada", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "close_on_timeout": "S'ha aturat perquè s'ha acabat el temps d'espera", + "closing_automatically": "S'ha aturat perquè ha acabat la descàrrega", + "timeout_download_still_running": "S'està esperant que acabi la descàrrega", + "large_filesize": "Compte: La transferència d'arxius molt grans podria trigar hores", "systray_menu_exit": "Surt", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", + "systray_download_started_title": "S'ha iniciat la descàrrega amb OnionShare", + "systray_download_started_message": "Algú ha començat a descarregar els teus arxius", + "systray_download_completed_title": "S'ha completat la descàrrega amb OnionShare", + "systray_download_completed_message": "Algú ha acabat de descarregar els teus arxius", + "systray_download_canceled_title": "S'ha canceŀlat la descàrrega", "systray_download_canceled_message": "L'usuari va cancel·lar la descàrrega", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", + "systray_upload_started_title": "S'ha iniciat la pujada", + "systray_upload_started_message": "Algú ha començat a pujar arxius al teu ordinador", + "help_local_only": "No facis servir Tor (només per a desenvolupament)", + "help_stay_open": "Manté obert el servei després de la primera descàrrega", + "help_shutdown_timeout": "Deixa de compartir al cap de tants segons", + "help_stealth": "Fes servir autorització de client (avançat)", + "help_receive": "Rep recursos en comptes d'enviar-los", + "help_debug": "Envia els errors d'OnionShare a stdout i els errors web al disc", + "help_filename": "Llista d'arxius o carpetes a compartir", + "help_config": "Ubicació de la configuració JSON personalitzada", + "gui_drag_and_drop": "Arrossega arxius i carpetes\nper començar a compartir", + "gui_add": "Afegeix", + "gui_delete": "Esborra", + "gui_choose_items": "Escull", + "gui_share_start_server": "Comparteix", + "gui_share_stop_server": "Deixa de compartir", + "gui_share_stop_server_shutdown_timeout": "Deixa de compartir (queden {}s)", + "gui_share_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}", + "gui_receive_start_server": "Inicia en mode de recepció", + "gui_receive_stop_server": "Atura el mode de recepció", + "gui_receive_stop_server_shutdown_timeout": "Atura el mode de recepció (queden {}s)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}", + "gui_copy_url": "Copia l'adreça", + "gui_copy_hidservauth": "Copia el HidServAuth", + "gui_downloads": "Historial de descàrregues", + "gui_no_downloads": "No n'hi ha cap", + "gui_canceled": "Canceŀlat", + "gui_copied_url_title": "S'ha copiat l'adreça OnionShare", + "gui_copied_url": "S'ha copiat l'adreça OnionShare al porta-retalls", + "gui_copied_hidservauth_title": "S'ha copiat el HidServAuth", + "gui_copied_hidservauth": "S'ha copiat la línia HidServAuth al porta-retalls", + "gui_please_wait": "S'està iniciant… Clica per a canceŀlar.", + "gui_download_upload_progress_complete": "Han passat %p%, {0:s}.", + "gui_download_upload_progress_starting": "{0:s}, %p% (s'està calculant)", + "gui_download_upload_progress_eta": "{0:s}, temps restant: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Espera un moment", + "gui_share_quit_warning": "Encara s'estan enviant fitxers. Segur que vols sortir de l'OnionShare?", + "gui_receive_quit_warning": "Encara s'estan rebent arxius. Segur que vols sortir de l'OnionShare?", + "gui_quit_warning_quit": "Surt", + "gui_quit_warning_dont_quit": "Canceŀla", + "error_rate_limit": "Algú ha fet massa intents a la teva adreça, cosa que podria significar que l'estan intentant endevinar. Per això OnionShare s'ha aturat sola. Pots tornar a començar i enviar a la destinatària la nova adreça.", + "zip_progress_bar_format": "S'està comprimint: %p%", + "error_stealth_not_supported": "Per fer servir l'autorització de client, necessites les versions iguals o superiors a Tor 0.2.9.1-alpha (o Tor Browser 6.5) i python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare necessita almenys les versions Tor 0.2.7.1 i python3-stem 1.4.0.", + "gui_settings_window_title": "Configuració", + "gui_settings_whats_this": "Què és això?", + "gui_settings_stealth_option": "Fes servir autorització de client (antiquada)", + "gui_settings_stealth_hidservauth_string": "Ara que has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu HidServAuth.", + "gui_settings_autoupdate_label": "Comprova si hi ha noves versions", + "gui_settings_autoupdate_option": "Notifica'm si hi ha una actualització disponible", + "gui_settings_autoupdate_timestamp": "Última comprovació: {}", + "gui_settings_autoupdate_timestamp_never": "Mai", + "gui_settings_autoupdate_check_button": "Comprova si hi ha una versió més nova", + "gui_settings_general_label": "Configuració general", + "gui_settings_sharing_label": "Configuració de compartir", + "gui_settings_close_after_first_download_option": "Deixa de compartir després de la primera descàrrega", + "gui_settings_connection_type_label": "Com hauria de connectar-se OnionShare a Tor?", + "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare", + "gui_settings_connection_type_automatic_option": "Intenta la configuració automàtica amb el Navegador Tor", + "gui_settings_connection_type_control_port_option": "Connecta fent servir el port de control", + "gui_settings_connection_type_socket_file_option": "Connecta fent servir un arxiu de socket", + "gui_settings_connection_type_test_button": "Comprova la connexió a Tor", + "gui_settings_control_port_label": "Port de control", + "gui_settings_socket_file_label": "Arxiu de socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Configuració d'autenticació a Tor", + "gui_settings_authenticate_no_auth_option": "Sense autenticació o autenticació per cookies", + "gui_settings_authenticate_password_option": "Contrasenya", + "gui_settings_password_label": "Contrasenya", + "gui_settings_tor_bridges": "Ponts de Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "No facis servir ponts", + "gui_settings_tor_bridges_obfs4_radio_option": "Fes servir el transport connectable obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Fes servir el transport connectable obfs4 (necessita obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Fes servir el transport connectable meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Fes servir el transport connectable meek_lite (Azure, necessita obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Compte: els ponts meek_lite costen molts recursos al Tor Project per funcionar.

    Sisplau, fes-los servir només si no pots connectar-te a Tor directament, a través de obfs4, o a través de ponts normals.", + "gui_settings_tor_bridges_custom_radio_option": "Fes servir ponts personalitzats", + "gui_settings_tor_bridges_custom_label": "Pots trobar-ne a https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Cap dels ponts que has afegit ha funcionat.\nComprova'ls o prova d'afegir-ne de nous.", + "gui_settings_button_save": "Desa", + "gui_settings_button_cancel": "Canceŀla", + "gui_settings_button_help": "Ajuda", + "gui_settings_shutdown_timeout_checkbox": "Posa un temporitzador d'aturada", + "gui_settings_shutdown_timeout": "Atura a:", + "settings_error_unknown": "No s'ha pogut connectar a Tor perquè la configuració és inconsistent.", + "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a https://torproject.org)", + "settings_error_socket_port": "No s'ha pogut establir la connexió al controlador de Tor a {}:{}.", + "settings_error_socket_file": "No s'ha pogut connectar al controlador de Tor fent servir el fitxer de socket {}.", + "settings_error_auth": "S'ha establert la connexió a {}:{} però ha fallat l'autenticació. Pot ser que no sigui un controlador de Tor?", + "settings_error_missing_password": "S'ha establer la connexió al controlador de Tor, però necessita una contrasenya d'autenticació.", + "settings_error_unreadable_cookie_file": "S'ha establert la connexió al controlador de Tor, però hi ha hagut un error de permisos. Pot ser que la contrasenya sigui errònia o que faltin permisos de lectura a l'arxiu de cookie.", + "settings_error_bundled_tor_not_supported": "La versió de Tor inclosa a OnionShare no funciona en mode de desenvolupador a Windows ni MacOS.", + "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegura't que estàs connectat a internet i que tens en hora el rellotge del sistema.", + "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}", "settings_test_success": "", "error_tor_protocol_error": "", "error_tor_protocol_error_unknown": "", @@ -181,5 +181,6 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "S'està esperant que acabi la pujada" } -- cgit v1.2.3-54-g00ecf From b40f370790c97da096b1613edf0db712b2ca83ae Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Thu, 6 Dec 2018 03:27:05 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/da.json b/share/locale/da.json index 5f198098..b759cdc4 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -184,5 +184,6 @@ "receive_mode_warning": "Advarsel: Modtagetilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", - "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}" + "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}", + "timeout_upload_still_running": "Venter på at upload skal blive færdig" } -- cgit v1.2.3-54-g00ecf From 32ea55b3e21ba4d231563b1ef320012c11a3e26b Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Thu, 6 Dec 2018 06:41:04 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/no.json b/share/locale/no.json index d0b2661f..4d474cea 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -185,5 +185,6 @@ "history_completed_tooltip": "{} fullført", "gui_download_in_progress": "Nedlasting startet {}", "gui_settings_language_label": "Foretrukket språk", - "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg." + "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg.", + "timeout_upload_still_running": "Venter på at opplastingen fullføres" } -- cgit v1.2.3-54-g00ecf From 602908d542a08c2a69014ddbc7f5a6f41144973d Mon Sep 17 00:00:00 2001 From: fadelkon Date: Thu, 6 Dec 2018 13:30:58 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index 9d1669d3..3477fd3b 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -149,7 +149,7 @@ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes enchufables obfs4 incorporados (requiere obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    br>Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", "gui_settings_button_save": "Guardar", -- cgit v1.2.3-54-g00ecf From caf31090bf1456d9f4ff93deabeb0e98f08e282a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 8 Dec 2018 11:25:06 -0800 Subject: Split pt locale into pt_BR and pt_PT --- onionshare/settings.py | 3 ++- share/locale/pt.json | 7 ------- share/locale/pt_BR.json | 7 +++++++ share/locale/pt_PT.json | 7 +++++++ 4 files changed, 16 insertions(+), 8 deletions(-) delete mode 100644 share/locale/pt.json create mode 100644 share/locale/pt_BR.json create mode 100644 share/locale/pt_PT.json diff --git a/onionshare/settings.py b/onionshare/settings.py index 02f644af..2738ae39 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -62,7 +62,8 @@ class Settings(object): 'it': 'Italiano', # Italian 'nl': 'Nederlands', # Dutch 'no': 'Norsk', # Norweigan - 'pt': 'Português', # Portuguese + 'pt_BR': 'Português Brasil', # Portuguese Brazil + 'pt_PT': 'Português Portugal', # Portuguese Portugal 'ru': 'Русский', # Russian 'tr': 'Türkçe' # Turkish } diff --git a/share/locale/pt.json b/share/locale/pt.json deleted file mode 100644 index 1b2d3139..00000000 --- a/share/locale/pt.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", - "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", - "not_a_file": "{0:s} não é um arquivo.", - "gui_copied_url": "URL foi copiado para área de transferência", - "other_page_loaded": "Outra página tem sido carregada" -} diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json new file mode 100644 index 00000000..1b2d3139 --- /dev/null +++ b/share/locale/pt_BR.json @@ -0,0 +1,7 @@ +{ + "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", + "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "not_a_file": "{0:s} não é um arquivo.", + "gui_copied_url": "URL foi copiado para área de transferência", + "other_page_loaded": "Outra página tem sido carregada" +} diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json new file mode 100644 index 00000000..1b2d3139 --- /dev/null +++ b/share/locale/pt_PT.json @@ -0,0 +1,7 @@ +{ + "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", + "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "not_a_file": "{0:s} não é um arquivo.", + "gui_copied_url": "URL foi copiado para área de transferência", + "other_page_loaded": "Outra página tem sido carregada" +} -- cgit v1.2.3-54-g00ecf From 7ea5a1394ad16ae328b02c443c03e7473898e54d Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 10 Dec 2018 10:29:53 +0000 Subject: Added translation using Weblate (Korean) --- share/locale/ko.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/ko.json diff --git a/share/locale/ko.json b/share/locale/ko.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/ko.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 1cf816b24bf61be1067f1ebff338920d76d1352a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 10 Dec 2018 07:18:25 -0800 Subject: Fixed typo in comment --- onionshare/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 2738ae39..fc68ffc9 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -61,7 +61,7 @@ class Settings(object): 'fr': 'Français', # French 'it': 'Italiano', # Italian 'nl': 'Nederlands', # Dutch - 'no': 'Norsk', # Norweigan + 'no': 'Norsk', # Norwegian 'pt_BR': 'Português Brasil', # Portuguese Brazil 'pt_PT': 'Português Portugal', # Portuguese Portugal 'ru': 'Русский', # Russian -- cgit v1.2.3-54-g00ecf From adfb84383a16969084877639ba29946a205cf395 Mon Sep 17 00:00:00 2001 From: Adrià García-Alzórriz Date: Sun, 9 Dec 2018 11:39:38 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 40b6bac2..2afe214f 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -116,11 +116,11 @@ "settings_error_bundled_tor_not_supported": "La versió de Tor inclosa a OnionShare no funciona en mode de desenvolupador a Windows ni MacOS.", "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegura't que estàs connectat a internet i que tens en hora el rellotge del sistema.", "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", + "settings_test_success": "Connectat al controlador de Tor.\n\nVersió de Tor: {}\nSuporta serveis onion efímers: {}.\nSuporta autenticació del client: {}.\nSuporta adreces .onion de nova generació: {}.", + "error_tor_protocol_error": "Hi ha hagut un error amb Tor: {}", + "error_tor_protocol_error_unknown": "Hi ha hagut un error desconegut amb Tor", + "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", + "connecting_to_tor": "Connectant a la xarxa Tor", "update_available": "", "update_error_check_error": "", "update_error_invalid_latest_version": "", -- cgit v1.2.3-54-g00ecf From 655e6fde0fe9d1ef3817870d8f23cb1c12ba6666 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 10 Dec 2018 10:26:23 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index 2afe214f..fbe11606 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -107,7 +107,7 @@ "gui_settings_shutdown_timeout_checkbox": "Posa un temporitzador d'aturada", "gui_settings_shutdown_timeout": "Atura a:", "settings_error_unknown": "No s'ha pogut connectar a Tor perquè la configuració és inconsistent.", - "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a https://torproject.org)", + "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a torproject.org)", "settings_error_socket_port": "No s'ha pogut establir la connexió al controlador de Tor a {}:{}.", "settings_error_socket_file": "No s'ha pogut connectar al controlador de Tor fent servir el fitxer de socket {}.", "settings_error_auth": "S'ha establert la connexió a {}:{} però ha fallat l'autenticació. Pot ser que no sigui un controlador de Tor?", -- cgit v1.2.3-54-g00ecf From f7964ce0da3d29680ded6bcb236a6bd4853515d0 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 10 Dec 2018 08:59:13 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index 87b873c7..7e075aaf 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,27 +1,27 @@ { - "preparing_files": "Comprimere i files.", - "give_this_url": "condividi questo URL:", - "ctrlc_to_stop": "Premi Ctrl-C per terminare", + "preparing_files": "Preparazione dei files da condividere.", + "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", + "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", - "other_page_loaded": "URL pronto", - "closing_automatically": "download completato, chiudo", + "other_page_loaded": "URL caricato", + "closing_automatically": "Chiusura automatica dopo aver finito il download", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare Tor (solo per lo sviluppatori)", - "help_stay_open": "Continua condividendo anche dopo il download", - "help_debug": "Mostra gli errori sullo schermo e salva gli errori web su file", + "help_local_only": "Non usare tor: è solo per lo sviluppo", + "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", + "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", - "gui_drag_and_drop": "Sposta qui file e cartelle \nper iniziare a condividere", + "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", "gui_add": "Aggiungi", "gui_delete": "Cancella", - "gui_choose_items": "Seleziona", - "gui_share_start_server": "Inizia a condividere", + "gui_choose_items": "Scegli", + "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", - "gui_copy_url": "Copia URL", - "gui_downloads": "Downloads:", + "gui_copy_url": "Copia l' URL", + "gui_downloads": "Cronologia Dei Download", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", - "gui_please_wait": "Attendere prego... fai clic per interrompere.", - "zip_progress_bar_format": "Elaborazione del file: %p%", + "gui_please_wait": "Attendere prego...", + "zip_progress_bar_format": "Elaborazione files: %p%", "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al recipiente:", "give_this_url_receive": "Dai questo indirizzo al mittente:", @@ -58,7 +58,7 @@ "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org", "gui_quit_title": "Non cosí in fretta", - "gui_share_quit_warning": "Stai condividendo dei file, vuoi davvero terminare e chiudere OnionShare?", + "gui_share_quit_warning": "Stai per inviare dai file. Sei sicuro di voler uscire da OnionShare?", "gui_receive_quit_warning": "Stai ricevendo file, vuoi davvero terminare e chiudere OnionShare?", "gui_quit_warning_quit": "Esci", "gui_quit_warning_dont_quit": "Cancella", -- cgit v1.2.3-54-g00ecf From 9c04600d685a1841af99cac84bde010d4ff2fbe3 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 10 Dec 2018 10:23:53 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index 3477fd3b..c2d89282 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -185,5 +185,6 @@ "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", - "gui_upload_finished_range": "Cargado {} a {}" + "gui_upload_finished_range": "Cargado {} a {}", + "timeout_upload_still_running": "Esperando a que se complete la subida" } -- cgit v1.2.3-54-g00ecf From a322c3fbaa197c4ca8d810ebec67e08d0b0c13c9 Mon Sep 17 00:00:00 2001 From: Mesut Akcan Date: Mon, 10 Dec 2018 09:06:20 +0000 Subject: Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ --- share/locale/tr.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/share/locale/tr.json b/share/locale/tr.json index 68807410..ae6a7058 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,7 +1,7 @@ { - "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", - "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", - "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", + "preparing_files": "Sıkıştırma dosyaları.", + "give_this_url": "Bu adresi alıcıya verin:", + "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", "other_page_loaded": "Diğer sayfa yüklendi", "closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor", @@ -21,5 +21,6 @@ "gui_canceled": "İptal edilen", "gui_copied_url": "Panoya kopyalanan URL", "gui_please_wait": "Lütfen bekleyin...", - "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" + "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%", + "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla." } -- cgit v1.2.3-54-g00ecf From 90b907932aede43cb10006b000ccaec80280ca06 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 10 Dec 2018 17:57:25 +0000 Subject: Added translation using Weblate (Bengali) --- share/locale/bn.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/bn.json diff --git a/share/locale/bn.json b/share/locale/bn.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/bn.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From af57803ade92b87aaf841425bae5d6b78851f86f Mon Sep 17 00:00:00 2001 From: emma peel Date: Tue, 11 Dec 2018 07:58:44 +0000 Subject: translations mostly completed (up to 84% translated) --- share/locale/da.json | 222 ++++++++++++++++++++++++++++++++++----------------- share/locale/de.json | 168 +++++++++++++++++++++++++++++++++++--- share/locale/es.json | 190 ++++++++++++++++++++++++++++++++++++++++--- share/locale/fr.json | 164 ++++++++++++++++++++++++++++++++----- share/locale/ga.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/no.json | 191 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 1002 insertions(+), 118 deletions(-) create mode 100644 share/locale/ga.json diff --git a/share/locale/da.json b/share/locale/da.json index d414695b..b759cdc4 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -1,115 +1,189 @@ { - "config_onion_service": "Konfigurerer onion-tjeneste på port {0:d}.", - "preparing_files": "Forbereder filer som skal deles.", - "give_this_url": "Giv denne URL til personen du sender filen til:", - "give_this_url_stealth": "Giv denne URL og HidServAuth-linje til personen du sender filen til:", - "ctrlc_to_stop": "Tryk på Ctrl-C for at stoppe serveren", + "config_onion_service": "Opsætter onion-tjeneste på port {0:d}.", + "preparing_files": "Komprimerer filer.", + "give_this_url": "Giv adressen til modtageren:", + "give_this_url_stealth": "Giv adressen og HidServAuth-linjen til modtageren:", + "ctrlc_to_stop": "Tryk på Ctrl+C for at stoppe serveren", "not_a_file": "{0:s} er ikke en gyldig fil.", "not_a_readable_file": "{0:s} er ikke en læsbar fil.", - "no_available_port": "Kunne ikke starte onion-tjenesten da der ikke var nogen tilgængelig port.", - "other_page_loaded": "URL indlæst", - "close_on_timeout": "Lukker automatisk da timeout er nået", - "closing_automatically": "Lukker automatisk da download er færdig", - "timeout_download_still_running": "Venter på at download skal blive færdig inden automatisk stop", - "large_filesize": "Advarsel: Det kan tage timer at sende store filer", + "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten", + "other_page_loaded": "Adresse indlæst", + "close_on_timeout": "Stoppede fordi timer med autostop løb ud", + "closing_automatically": "Stoppede fordi download er færdig", + "timeout_download_still_running": "Venter på at download skal blive færdig", + "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling", "systray_menu_exit": "Afslut", - "systray_download_started_title": "OnionShare-download startet", - "systray_download_started_message": "En bruger startede download af dine filer", + "systray_download_started_title": "OnionShare-download begyndte", + "systray_download_started_message": "En bruger begyndte download af dine filer", "systray_download_completed_title": "OnionShare-download færdig", "systray_download_completed_message": "Brugeren er færdig med at downloade dine filer", "systray_download_canceled_title": "OnionShare-download annulleret", "systray_download_canceled_message": "Brugeren annullerede downloaden", - "help_local_only": "Undlad at bruge tor: kun til udvikling", - "help_stay_open": "Hold onion-tjeneste kørende efter download er færdig", - "help_shutdown_timeout": "Luk onion-tjenesten efter N sekunder", - "help_stealth": "Opret usynlig onion-tjeneste (avanceret)", - "help_debug": "Log programfejl til stdout, og log webfejl til disk", + "help_local_only": "Brug ikke Tor (kun til udvikling)", + "help_stay_open": "Bliv ved med at dele efter første download", + "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", + "help_stealth": "Brug klientautentifikation (avanceret)", + "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", "help_filename": "Liste over filer eller mapper som skal deles", - "help_config": "Sti til en brugerdefineret JSON-konfigurationsfil (valgfri)", - "gui_drag_and_drop": "Træk og slip\nfiler her", + "help_config": "Tilpasset placering af JSON-konfigurationsfil (valgfri)", + "gui_drag_and_drop": "Træk og slip filer og mapper her\nfor at starte deling", "gui_add": "Tilføj", "gui_delete": "Slet", "gui_choose_items": "Vælg", - "gui_share_start_server": "Start deling", + "gui_share_start_server": "Begynd at dele", "gui_share_stop_server": "Stop deling", - "gui_copy_url": "Kopiér URL", + "gui_copy_url": "Kopiér adresse", "gui_copy_hidservauth": "Kopiér HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Downloadhistorik", "gui_canceled": "Annulleret", - "gui_copied_url": "Kopierede URL til udklipsholder", - "gui_copied_hidservauth": "Kopierede HidServAuth-linje til udklipsholder", - "gui_please_wait": "Vent venligst...", - "gui_download_upload_progress_complete": "%p%, tid forløbet: {0:s}", + "gui_copied_url": "OnionShare-adressen blev kopieret til udklipsholderen", + "gui_copied_hidservauth": "HidServAuth-linjen blev kopieret til udklipsholderen", + "gui_please_wait": "Starter... klik for at annullere.", + "gui_download_upload_progress_complete": ".", "gui_download_upload_progress_starting": "{0:s}, %p% (udregner anslået ankomsttid)", - "gui_download_upload_progress_eta": "{0:s}, anslået ankomsttid: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Er du sikker på, at du vil afslutte?\nURL'en som du deler vil ikke eksistere længere.", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_share_quit_warning": "Du er ved at afsende filer. Er du sikker på, at du vil afslutte OnionShare?", "gui_quit_warning_quit": "Afslut", - "gui_quit_warning_dont_quit": "Afslut ikke", - "error_rate_limit": "En angriber forsøger måske at gætte din URL. For at forhindre det, har OnionShare automatisk stoppet serveren. For at dele filerne skal du starte den igen og dele den nye URL.", - "zip_progress_bar_format": "Databehandler filer: %p%", - "error_stealth_not_supported": "For at oprette usynlige onion-tjenester, skal du mindst have Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og mindst python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare kræver mindst Tor 0.2.7.1 og mindst python3-stem 1.4.0.", + "gui_quit_warning_dont_quit": "Annuller", + "error_rate_limit": "Nogen har foretaget for mange forkerte forsøg på din adresse, hvilket kan betyde at de prøver at gætte det, så OnionShare har stoppet serveren. Start deling igen og send en ny adresse til modtageren for at dele.", + "zip_progress_bar_format": "Komprimerer: %p%", + "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_option": "Opret usynlige onion-tjenester", - "gui_settings_stealth_hidservauth_string": "Du har gemt den private nøgle til at blive brugt igen, så din HidServAuth-streng bruges også igen.\nKlik nedenfor, for at kopiere HidServAuth.", - "gui_settings_autoupdate_label": "Søg efter opdateringer", - "gui_settings_autoupdate_option": "Giv mig besked når der findes opdateringer", + "gui_settings_stealth_option": "Brug klientautentifikation (forældet)", + "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu\nkan klikke for at kopiere din HidServAuth.", + "gui_settings_autoupdate_label": "Søg efter ny version", + "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", "gui_settings_autoupdate_timestamp_never": "Aldrig", - "gui_settings_autoupdate_check_button": "Søg efter opdateringer", - "gui_settings_sharing_label": "Valgmuligheder for deling", + "gui_settings_autoupdate_check_button": "Søg efter ny version", + "gui_settings_sharing_label": "Delingsindstillinger", "gui_settings_close_after_first_download_option": "Stop deling efter første download", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", - "gui_settings_connection_type_bundled_option": "Brug Tor som er bundet med OnionShare", - "gui_settings_connection_type_automatic_option": "Prøv automatisk konfiguration med Tor Browser", + "gui_settings_connection_type_bundled_option": "Brug Tor-versionen som er indbygget i OnionShare", + "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Opret forbindelse med kontrolport", "gui_settings_connection_type_socket_file_option": "Opret forbindelse med sokkelfil", - "gui_settings_connection_type_test_button": "Test Tor-indstillinger", + "gui_settings_connection_type_test_button": "Test forbindelsen til Tor", "gui_settings_control_port_label": "Kontrolport", "gui_settings_socket_file_label": "Sokkelfil", "gui_settings_socks_label": "SOCKS-port", - "gui_settings_authenticate_label": "Valgmuligheder for Tor-autentifikation", + "gui_settings_authenticate_label": "Indstillinger for Tor-autentifikation", "gui_settings_authenticate_no_auth_option": "Ingen autentifikation, eller cookieautentifikation", "gui_settings_authenticate_password_option": "Adgangskode", "gui_settings_password_label": "Adgangskode", "gui_settings_tor_bridges": "Understøttelse af Tor-bro", "gui_settings_tor_bridges_no_bridges_radio_option": "Brug ikke broer", - "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbygget obfs4 udskiftelige transporter", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbygget obfs4 udskiftelige transporter (kræver obfs4proxy)", - "gui_settings_tor_bridges_custom_radio_option": "Brug brugerdefinerede broer", + "gui_settings_tor_bridges_obfs4_radio_option": "Brug indbyggede obfs4 udskiftelige transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Brug indbyggede obfs4 udskiftelige transporter (kræver obfs4proxy)", + "gui_settings_tor_bridges_custom_radio_option": "Brug tilpassede broer", "gui_settings_tor_bridges_custom_label": "Du kan få broer fra https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Ingen af broerne du leverede ser ud til at være gyldige, så de ignoreres.\nPrøv venligst igen med gyldige broer.", + "gui_settings_tor_bridges_invalid": "Ingen at de broer du tilføjede virker.\nDobbeltklik på dem eller tilføj andre.", "gui_settings_button_save": "Gem", "gui_settings_button_cancel": "Annuller", "gui_settings_button_help": "Hjælp", "gui_settings_shutdown_timeout": "Stop delingen ved:", "settings_saved": "Indstillinger gemt til {}", - "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da indstillingerne ikke giver mening.", - "settings_error_automatic": "Kan ikke oprette forbindelse til Tor-kontroller. Kører Tor Browser i baggrunden? Hvis du ikke har den kan du få den fra:\nhttps://www.torproject.org/.", - "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontroller på {}:{}", - "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontroller med sokkelfilen {}", - "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det en Tor-kontroller?", - "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere", - "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontroller, men kan ikke autentificere da din adgangskode kan være forkert, og din bruger ikke har tilladelse til at læse cookiefilen.", - "settings_error_bundled_tor_not_supported": "Bundet Tor understøttes ikke når der ikke bruges udviklertilstand i Windows eller MacOS.", - "settings_error_bundled_tor_timeout": "Det tager for længe at oprette forbindelse til Tor. Din computer er måske offline, eller dit ur går forkert.", - "settings_error_bundled_tor_broken": "Der er noget galt med OnionShare som opretter forbindelse til Tor i baggrunden:\n{}", - "settings_test_success": "Tillykke, OnionShare kan oprette forbindelse til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}\nUnderstøtter usynlige onion-tjenester: {}", - "error_tor_protocol_error": "Fejl under snak med Tor-kontrolleren.\nHvis du bruger Whonix, så tjek https://www.whonix.org/wiki/onionshare for at få OnionShare til at virke.", - "connecting_to_tor": "Forbundet til Tor-netværket", - "update_available": "Der findes en OnionShare-opdatering. Klik her for at downloade den.

    Installeret version: {}
    Seneste version: {}", - "update_error_check_error": "Fejl under søgning efter opdateringer: Du er måske ikke forbundet til Tor, eller måske er OnionShare-webstedet nede.", - "update_error_invalid_latest_version": "Fejl under søgning efter opdateringer: OnionShare-webstedet svarende ved at sige at den seneste version er '{}', men det ser ikke ud til at være en gyldig versionsstreng.", - "update_not_available": "Du kører den seneste version af OnionShare.", - "gui_tor_connection_ask": "Vil du åbne OnionShare-indstillinger for at fejlsøge forbindelsen til Tor?", - "gui_tor_connection_ask_open_settings": "Åbn indstillinger", + "settings_error_unknown": "Kan ikke oprette forbindelse til Tor-kontroller da dine indstillingerne ikke giver mening.", + "settings_error_automatic": "Kunne ikke oprette forbindelse til Tor-kontrolleren. Kører Tor Browser (tilgængelige fra torproject.org) i baggrunden?", + "settings_error_socket_port": "Kan ikke oprette forbindelse til Tor-kontrolleren på {}:{}.", + "settings_error_socket_file": "Kan ikke oprette forbindelse til Tor-kontrolleren med sokkelfilen {}.", + "settings_error_auth": "Forbundet til {}:{}, men kan ikke autentificere. Er det fordi det ikke er en Tor-kontroller?", + "settings_error_missing_password": "Forbundet til Tor-kontroller, men den kræver en adgangskode for at autentificere.", + "settings_error_unreadable_cookie_file": "Forbundet til Tor-kontrolleren, men adgangskoden kan være forkert, eller din bruger har ikke tilladelse til at læse cookiefilen.", + "settings_error_bundled_tor_not_supported": "Brug af Tor-versionen som kom med OnionShare virker ikke i udviklertilstand på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "For længe om at oprette forbindelse til Tor. Måske har du ikke forbindelse til internettet, eller går dit systems ur forkert?", + "settings_error_bundled_tor_broken": "OnionShare kunne ikke oprette forbindelse til Tor i baggrunden:\n{}", + "settings_test_success": "Forbundet til Tor-kontrolleren.\n\nTor version: {}\nUnderstøtter kortvarige onion-tjenester: {}.\nUnderstøtter klientautentifikation: {}.\nUnderstøtter næste generations .onion-adresser: {}.", + "error_tor_protocol_error": "Der opstod en fejl med Tor: {}", + "connecting_to_tor": "Opretter forbindelse til Tor-netværket", + "update_available": "Der findes en ny OnionShare. Klik her for at hente den.

    Du bruger {} og den seneste er {}.", + "update_error_check_error": "Kunne ikke søge efter nye versioner: OnionShare-webstedet siger at den seneste version er den ugenkendte '{}'…", + "update_error_invalid_latest_version": "Kunne ikke søge efter ny version: Måske har du ikke forbindelse til Tor, eller er OnionShare-webstedet nede?", + "update_not_available": "Du kører den seneste OnionShare.", + "gui_tor_connection_ask": "Åbn indstillingerne for at rette forbindelsen til Tor?", + "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afslut", - "gui_tor_connection_error_settings": "Prøv at justere måden hvorpå OnionShare opretter forbindelse til Tor-netværket i Indstillinger.", - "gui_tor_connection_canceled": "OnionShare kan ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at konfigurere Tor-forbindelsen.", - "gui_tor_connection_lost": "Afbryder forbindelsen fra Tor.", - "gui_server_started_after_timeout": "Serveren startede efter dit valgte automatiske timeout.\nStart venligst en ny deling.", - "gui_server_timeout_expired": "Den valgte timeout er allerede udløbet.\nOpdater venligst timeouten og herefter kan du starte deling.", + "gui_tor_connection_error_settings": "Prøv at ændre måden hvorpå OnionShare opretter forbindelse til Tor-netværket, i indstillingerne.", + "gui_tor_connection_canceled": "Kunne ikke oprette forbindelse til Tor.\n\nSørg for at du har forbindelse til internettet, og åbn herefter OnionShare igen for at opsætte dens forbindelse til Tor.", + "gui_tor_connection_lost": "Der er ikke oprettet forbindelse til Tor.", + "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", + "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende URL\n(fravalg vil slette gemte URL)" + "gui_save_private_key_checkbox": "Brug en vedvarende adresse (udgået)", + "gui_copied_url_title": "Kopierede OnionShare-adresse", + "gui_copied_hidservauth_title": "Kopierede HidServAuth", + "gui_quit_title": "Klap lige hesten", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Brug indbyggede meek_lite (Azure) udskiftelige transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Brug indbyggede meek_lite (Azure) udskiftelige transporter (kræver obfs4proxy)", + "gui_settings_shutdown_timeout_checkbox": "Brug timer med autostop", + "gui_url_label_persistent": "Delingen stopper ikke automatisk.

    Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).", + "gui_url_label_stay_open": "Delingen stopper ikke automatisk.", + "gui_url_label_onetime": "Delingen stopper efter den første download.", + "gui_url_label_onetime_and_persistent": "Delingen stopper ikke automatisk.

    Hver efterfølgende deling bruger den samme adresse igen (hvis du vil bruge engangsadresser, så deaktivér \"Brug vedvarende adresse\", i indstillingerne).", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "info_in_progress_downloads_tooltip": "{} igangværende downloads", + "info_completed_downloads_tooltip": "{} færdige downloads", + "give_this_url_receive": "Giv adressen til afsenderen:", + "give_this_url_receive_stealth": "Giv adressen og HidServAuth til afsenderen:", + "systray_upload_started_title": "OnionShare-upload begyndte", + "systray_upload_started_message": "En bruger begyndte at uploade filer til din computer", + "help_receive": "Modtager aktier i stedet for at sende dem", + "gui_share_stop_server_shutdown_timeout": "Stop deling ({}s tilbage)", + "gui_receive_quit_warning": "Du er i færd med at modtage filer. Er du sikker på du ønsker at stoppe med at OnionShare?", + "gui_settings_whats_this": "Hvad er dette?", + "gui_settings_general_label": "Generel opsætning", + "gui_upload_in_progress": "Upload begyndte {}", + "gui_download_in_progress": "Download begyndte {}", + "gui_share_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", + "gui_receive_start_server": "Start modtagetilstand", + "gui_receive_stop_server": "Stop modtagetilstand", + "gui_receive_stop_server_shutdown_timeout": "Stop modtagetilstand ({}s tilbage)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Timer med autostop slutter ved {}", + "gui_no_downloads": "Ingen downloads endnu", + "error_tor_protocol_error_unknown": "Der opstod en ukendt fejl med Tor", + "error_invalid_private_key": "Den private nøgletype understøttes ikke", + "gui_use_legacy_v2_onions_checkbox": "Brug forældede adresser", + "gui_status_indicator_share_stopped": "Klar til at dele", + "gui_status_indicator_share_working": "Starter…", + "gui_status_indicator_share_started": "Deler", + "gui_status_indicator_receive_stopped": "Klar til at modtage", + "gui_status_indicator_receive_working": "Starter…", + "gui_status_indicator_receive_started": "Modtager", + "receive_mode_received_file": "Modtaget: {}", + "gui_mode_share_button": "Del filer", + "gui_mode_receive_button": "Modtag filer", + "gui_settings_receiving_label": "Modtagelsesindstillinger", + "gui_settings_downloads_label": "Gem filer til", + "gui_settings_downloads_button": "Gennemse", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Modtagetilstand kan ikke stoppes af afsenderen", + "gui_settings_public_mode_checkbox": "Offentlig tilstand", + "systray_close_server_title": "OnionShare-server lukket", + "systray_close_server_message": "En bruger lukkede serveren", + "systray_page_loaded_title": "OnionShare-side indlæst", + "systray_download_page_loaded_message": "En bruger indlæste downloadsiden", + "systray_upload_page_loaded_message": "En bruger indlæste uploadsiden", + "gui_uploads": "Uploadhistorik", + "gui_no_uploads": "Ingen uploads endnu", + "gui_clear_history": "Ryd alle", + "gui_upload_finished_range": "Uploadede {} til {}", + "gui_upload_finished": "Uploadet {}", + "gui_settings_language_label": "Foretrukne sprog", + "gui_settings_language_changed_notice": "Genstart OnionShare for at din ændring af sprog skal træder i kraft.", + "gui_settings_meek_lite_expensive_warning": "Advarsel: meek_lite-broerne er meget dyre at køre for Tor-projektet.

    Brug dem kun hvis du ikke er i stand til at oprette forbindelse til Tor direkte, via obfs4-transporter eller andre normale broer.", + "gui_share_url_description": "Alle med OnionShare-adressen kan downloade dine filer med Tor Browser: ", + "gui_receive_url_description": "Alle med OnionShare-adressen kan uploade filer til din computer med Tor Browser: ", + "history_in_progress_tooltip": "{} igangværende", + "history_completed_tooltip": "{} færdige", + "info_in_progress_uploads_tooltip": "{} igangværende upload(s)", + "info_completed_uploads_tooltip": "{} upload(s) færdige", + "error_cannot_create_downloads_dir": "Kunne ikke oprette modtagetilstand-mappe: {}", + "receive_mode_downloads_dir": "Filer som sendes til dig vises i denne mappe: {}", + "receive_mode_warning": "Advarsel: Modtagetilstand lader folk uploade filer til din computer. Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", + "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", + "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}", + "timeout_upload_still_running": "Venter på at upload skal blive færdig" } diff --git a/share/locale/de.json b/share/locale/de.json index 1d0436a0..30f5eed0 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,23 +1,173 @@ { "preparing_files": "Dateien werden vorbereitet.", - "give_this_url": "Geben Sie diese URL der Person, der Sie die Datei zusenden möchten:", - "ctrlc_to_stop": "Drücken Sie Strg+C um den Server anzuhalten", + "give_this_url": "Gib diese URL an den Empfänger:", + "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine Datei.", "other_page_loaded": "URL geladen", - "closing_automatically": "Halte automatisch an, da der Download beendet wurde", + "closing_automatically": "Gestoppt, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", - "help_local_only": "Nicht mit Tor benutzen, nur für Entwicklung", - "help_stay_open": "Den onion service nicht anhalten nachdem ein Download beendet wurde", - "help_debug": "Fehler auf Festplatte schreiben", + "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", + "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", + "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler aus die Festplatte", "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", - "gui_drag_and_drop": "Drag & drop\nDateien hier", + "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", "gui_add": "Hinzufügen", "gui_delete": "Löschen", "gui_choose_items": "Auswählen", "gui_share_start_server": "Server starten", "gui_share_stop_server": "Server anhalten", "gui_copy_url": "URL kopieren", - "gui_downloads": "Downloads:", + "gui_downloads": "Bisherige Downloads", "gui_copied_url": "URL wurde in die Zwischenablage kopiert", - "gui_please_wait": "Bitte warten..." + "gui_please_wait": "Starte... Klicken zum Abbrechen.", + "timeout_download_still_running": "Warte auf Beendigung des Downloads", + "systray_menu_exit": "Beenden", + "gui_settings_authenticate_password_option": "Passwort", + "gui_settings_password_label": "Passwort", + "gui_settings_button_save": "Speichern", + "gui_settings_button_cancel": "Abbrechen", + "gui_settings_button_help": "Hilfe", + "gui_settings_shutdown_timeout": "Stoppe den Server bei:", + "systray_download_started_title": "OnionShareDownload begonnen", + "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", + "systray_download_completed_title": "OnionShare Download beendet", + "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", + "systray_download_canceled_title": "OnionShareDownload abgebrochen", + "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen", + "gui_copy_hidservauth": "HidServAuth kopieren", + "gui_canceled": "Abgebrochen", + "gui_copied_hidservauth_title": "HidServAuth kopiert", + "gui_quit_warning_quit": "Beenden", + "gui_quit_warning_dont_quit": "Abbrechen", + "gui_settings_window_title": "Eintellungen", + "gui_settings_autoupdate_timestamp": "Letzte Überprüfung: {}", + "gui_settings_autoupdate_timestamp_never": "Niemals", + "gui_settings_close_after_first_download_option": "Server nach dem ersten Download stoppen", + "gui_settings_connection_type_label": "Wie soll sich OnionShare mit Tor verbinden?", + "config_onion_service": "Richte den Onionservice auf Port {0:d} ein.", + "give_this_url_stealth": "Gib dem Empfänger diese URL und die HidServAuth-Zeile:", + "give_this_url_receive": "Gib diese URL dem Sender:", + "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", + "not_a_readable_file": "{0:s} kann nicht gelesen werden.", + "no_available_port": "Konnte keinen freien Port finden, um den Onionservice zu starten", + "close_on_timeout": "Wegen Zeitablaufs gestoppt", + "systray_upload_started_title": "OnionShare Upload gestartet", + "systray_upload_started_message": "Ein Nutzer hat begonnen Dateien auf deinen Computer zu laden", + "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", + "help_receive": "Empfange Dateien anstatt sie zu senden", + "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", + "gui_settings_connection_type_control_port_option": "Verbinde über den control port", + "gui_settings_connection_type_socket_file_option": "Verbinde über ein socket file", + "gui_settings_control_port_label": "Control port", + "gui_settings_socket_file_label": "Socket file", + "gui_settings_socks_label": "SOCKS Port", + "gui_settings_authenticate_no_auth_option": "Keine Authentifizierung, oder Authentifizierung per cookie", + "gui_settings_tor_bridges_no_bridges_radio_option": "Benutze keine bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Benutze eingebaute obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Benutze eingebaute obfs4 pluggable transports (benötigt obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Benutze eingebaute meek_lite (Amazon) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Benutze eingebaute meek_lite (Azure) pluggable transports (benötigt obfs4proxy)", + "gui_settings_tor_bridges_custom_radio_option": "Benutze benutzerdefinierte bridges", + "gui_settings_tor_bridges_custom_label": "Bridges findest du unter https://bridges.torproject.org", + "gui_settings_shutdown_timeout_checkbox": "Stoppe nach einer bestimmten Zeit", + "settings_error_auth": "Mit {}:{} verbinden aber nicht authentifiziert. Eventuell handelt es sich nicht um einen Tor controller?", + "settings_error_missing_password": "Mit dem Tor controller verbunden, aber er benötigt ein Passwort zur Authentifizierung.", + "connecting_to_tor": "Verbinde mit dem Tornetzwerk", + "gui_tor_connection_ask_quit": "Beenden", + "gui_tor_connection_lost": "Verbindung zu Tor getrennt.", + "help_stealth": "Nutze Klientauthorisierung (fortgeschritten)", + "gui_receive_start_server": "Starte den Empfängermodus", + "gui_receive_stop_server": "Stoppe den Empfängermodus", + "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {})", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", + "gui_no_downloads": "Bisher keine Downloads", + "gui_copied_url_title": "OnionShareadresse kopiert", + "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert", + "gui_download_upload_progress_complete": "%p%, {0:s} vergangen.", + "gui_download_upload_progress_starting": "{0:s}, %p% (berechne)", + "gui_download_upload_progress_eta": "{0:s}, Voraussichtliche Dauer: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Nicht so schnell", + "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", + "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", + "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", + "zip_progress_bar_format": "Packe: %p%", + "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", + "gui_settings_whats_this": "Was ist das?", + "gui_settings_stealth_option": "Nutze Klientauthorisierung (Alt)", + "gui_settings_autoupdate_label": "Suche nach einer neueren Version", + "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", + "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", + "gui_settings_general_label": "Allgemeine Einstellungen", + "gui_settings_sharing_label": "Servereinstellungen", + "gui_settings_connection_type_automatic_option": "Versuche mit dem Tor Browser automatisch zu konfigurieren", + "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", + "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", + "gui_settings_tor_bridges": "Einstellungen für Tor bridges", + "gui_settings_meek_lite_expensive_warning": "Achtung: Die meek_lite bridges sind für das Tor Projekt sehr kostspielig.

    Nutze sie nur, wenn du dich nicht direkt, per obfs4 Transport oder über andere, normale bridges zum Tornetzwerk verbinden kannst.", + "gui_settings_tor_bridges_invalid": "Keine der bridges, die du angegeben hast, funktionieren.\nÜberprüfe sie oder gebe Andere an.", + "settings_error_unknown": "Kann nicht zum Tor controller verbinden, weil deine Einstellungen keinen Sinn ergeben.", + "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", + "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", + "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer hat keine Rechte, die Cookiedatei zu lesen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare gekoppelt ist nicht nutzen.", + "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", + "settings_error_bundled_tor_broken": "OnionShare konnte nicht zu Tor im Hintergrund verbinden:\n{}", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion Adressen der nächsten Generation: {}.", + "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", + "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", + "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", + "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", + "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShareSeite sagt, die letzte Version ist die unkenntliche '{}'…", + "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?", + "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.", + "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk zu ermöglichen?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.", + "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", + "share_via_onionshare": "Teile es per OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", + "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse (alt)", + "gui_share_url_description": "Jeder mit dieser OnionShareAdresse kann deine Dateien mit dem Tor Browser herunterladen: ", + "gui_receive_url_description": "Jeder mit dieser OnionShareAdresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", + "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.

    Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)", + "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.", + "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.", + "gui_status_indicator_share_working": "Starte…", + "gui_status_indicator_share_started": "Läuft", + "gui_status_indicator_receive_stopped": "Bereit zum Empfangen", + "gui_status_indicator_receive_working": "Starte…", + "gui_status_indicator_receive_started": "Empfange", + "gui_file_info": "{} Dateien, {}", + "gui_file_info_single": "{} Datei, {}", + "history_completed_tooltip": "{} vollständige", + "info_in_progress_uploads_tooltip": "{} Upload(s) laufen", + "info_completed_uploads_tooltip": "{} Upload(s) vollständig", + "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", + "receive_mode_downloads_dir": "Dateien, die dir geschickt werden, findest du in diesem Ordner: {}", + "receive_mode_warning": "Achtung: Im Empfängermodus können Leute Dateien auf deinen Computer laden. Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "gui_receive_mode_warning": "Im Empfängermodus können Leute Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "receive_mode_received_file": "Empfangen: {}", + "gui_mode_share_button": "Versende Dateien", + "gui_mode_receive_button": "Empfange Dateien", + "gui_settings_receiving_label": "Empfangseinstellungen", + "gui_settings_downloads_label": "Speichere Dateien in", + "gui_settings_downloads_button": "Durchsuchen", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Der Empfängermodus kann vom Versender gestoppt werden", + "gui_settings_public_mode_checkbox": "Öffentlich", + "systray_close_server_title": "OnionShareServer gestoppt", + "systray_close_server_message": "Ein Nutzer hat den Server gestoppt", + "systray_download_page_loaded_message": "Ein Nutzer hat die Downloadseite geöffnet", + "systray_upload_page_loaded_message": "Ein Nutzer hat die Uploadseite geöffnet", + "gui_uploads": "Uploadhistorie", + "gui_no_uploads": "Bisher keine Uploads", + "gui_clear_history": "Alle löschen", + "gui_upload_in_progress": "Upload gestartet {}", + "gui_download_in_progress": "Download gestartet {}", + "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", + "gui_settings_language_label": "Bevorzugte Sprache", + "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", + "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)" } diff --git a/share/locale/es.json b/share/locale/es.json index 8c9945a8..3477fd3b 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -1,21 +1,189 @@ { - "preparing_files": "Preparando los archivos para compartir.", - "give_this_url": "Entregue esta URL a la persona a la que está enviando el archivo:", - "ctrlc_to_stop": "Pulse Ctrl-C para detener el servidor", + "preparing_files": "Comprimiendo los archivos.", + "give_this_url": "Entrega esta URL al receptor:", + "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo.", - "other_page_loaded": "La URL está lista.", + "other_page_loaded": "La URL está lista", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", - "help_local_only": "No intentar usar Tor: sólo para desarrollo", - "help_stay_open": "Mantener el servicio oculto ejecutando después de que la descarga haya finalizado", - "help_debug": "Guardar registro de errores en el disco", + "help_local_only": "No usar Tor (sólo para desarrollo)", + "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", + "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", "gui_drag_and_drop": "Arrastre\narchivos aquí", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", - "gui_share_start_server": "Encender el Servidor", - "gui_share_stop_server": "Detener el Servidor", + "gui_share_start_server": "Comenzar a compartir", + "gui_share_stop_server": "Dejar de compartir", "gui_copy_url": "Copiar URL", - "gui_downloads": "Descargas:", - "gui_copied_url": "Se copió la URL en el portapapeles" + "gui_downloads": "Historial de descargas", + "gui_copied_url": "Dirección OnionShare copiada al portapapeles", + "config_onion_service": "Configurando el servicio cebolla en el puerto {0:d}.", + "give_this_url_stealth": "Dale esta dirección y la línea de HidServAuth a la persona a la que le estás enviando el archivo:", + "no_available_port": "No se pudo iniciar el servicio cebolla porque no había puerto disponible", + "close_on_timeout": "Parado porque el temporizador expiró", + "timeout_download_still_running": "Esperando a que se complete la descarga", + "large_filesize": "Advertencia: Enviar un archivo tan grande podría llevar horas", + "help_shutdown_timeout": "Dejar de compartir después de una determinada cantidad de segundos", + "help_stealth": "Usar autorización de cliente (avanzada)", + "help_config": "Ubicación del archivo de configuración JSON personalizado (opcional)", + "gui_copied_url_title": "Dirección de OnionShare copiada", + "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles", + "gui_please_wait": "Comenzando.... Haz clic para cancelar.", + "gui_quit_title": "No tan rápido", + "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarlo, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", + "zip_progress_bar_format": "Comprimiendo: %p%", + "error_stealth_not_supported": "Para crear servicios de cebolla sigilosos, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requiere al menos Tor 0.2.7.1 y al menos python3-stem 1.4.0.", + "gui_settings_window_title": "Configuración", + "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", + "gui_settings_stealth_hidservauth_string": "Después de haber guardado su clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", + "gui_settings_autoupdate_label": "Control para versión nueva", + "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", + "gui_settings_autoupdate_check_button": "Buscar una Nueva Versión", + "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare", + "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor", + "gui_settings_connection_type_test_button": "Probar la conexión a Tor", + "gui_settings_tor_bridges": "Soporte de puentes de Tor", + "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", + "settings_saved": "Ajustes guardados en {}", + "give_this_url_receive": "Dale esta dirección al remitente:", + "give_this_url_receive_stealth": "Dar esta dirección y HidServAuth al remitente:", + "not_a_readable_file": "{0:s} no es un archivo legible.", + "systray_menu_exit": "Salir", + "systray_download_started_title": "Iniciada la descarga de OnionShare", + "systray_download_started_message": "Alguien comenzó a descargar tus archivos", + "systray_download_completed_title": "Descarga de OnionShare finalizada", + "systray_download_completed_message": "Alguien ha terminado de descargar tus archivos", + "systray_download_canceled_title": "Descarga de OnionShare Cancelada", + "systray_download_canceled_message": "El usuario canceló la descarga", + "systray_upload_started_title": "Subida OnionShare Iniciada", + "systray_upload_started_message": "Un usuario comenzó a subir archivos a tu computadora", + "help_receive": "Recibir recursos compartidos en lugar de enviarlos", + "gui_share_stop_server_shutdown_timeout": "Dejar de Compartir ({}s restantes)", + "gui_share_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}", + "gui_receive_start_server": "Iniciar el modo de recepción", + "gui_receive_stop_server": "Detener el modo de recepción", + "gui_receive_stop_server_shutdown_timeout": "Detener el modo de recepción ({}s restantes)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporizador de parada automática termina en {}", + "gui_copy_hidservauth": "Copiar HidServAuth", + "gui_no_downloads": "Ninguna Descarga Todavía", + "gui_canceled": "Cancelado", + "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", + "settings_error_unknown": "No se puede conectar al controlador Tor porque la configuración no tiene sentido.", + "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", + "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", + "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", + "settings_error_auth": "Conectado a {}:{}, pero no se puede autentificar. ¿Quizás este no sea un controlador Tor?", + "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero no puedo autenticarme porque la contraseña parece estar equivocada, y tu usuario no tiene permiso para leer el archivo cookie.", + "settings_error_bundled_tor_not_supported": "Bundled Tor no sepuede usar si no se usa el modo de desarrollo en Windows o macOS.", + "settings_error_bundled_tor_timeout": "Conectarse con Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado, o el reloj no está en hora?", + "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", + "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímeros: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", + "error_tor_protocol_error": "Hubo un error con Tor: {}", + "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", + "error_invalid_private_key": "Este tipo de clave privada no es compatible", + "connecting_to_tor": "Conexión a la red Tor", + "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Br>Estás usando {} y el último es {}.", + "update_error_check_error": "No se han podido comprobar las nuevas versiones: El sitio web de OnionShare dice que la última versión es la irreconocible '{}'.…", + "update_error_invalid_latest_version": "No se ha podido comprobar la nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Estás ejecutando la última versión de OnionShare.", + "gui_tor_connection_ask": "Abrir la configuración para arrreglar la conexión a Tor?", + "gui_tor_connection_ask_open_settings": "sí", + "gui_tor_connection_ask_quit": "Salir", + "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configura tu conexión a Tor.", + "gui_tor_connection_lost": "Desconectado de Tor.", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor, haz una nueva porción.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", + "share_via_onionshare": "Compártelo con OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Usar direcciones de legado", + "gui_save_private_key_checkbox": "Usar una dirección persistente (legado)", + "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Tor Browser: ", + "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Tor Browser: ", + "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", + "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_status_indicator_share_stopped": "Listo para compartir", + "gui_status_indicator_share_working": "Comenzando.…", + "gui_status_indicator_share_started": "Compartir", + "gui_status_indicator_receive_stopped": "Listo para recibir", + "gui_status_indicator_receive_working": "Comenzando.…", + "gui_status_indicator_receive_started": "Recibiendo", + "gui_file_info": "{} archivos, {}", + "gui_file_info_single": "{} archivo, {}", + "info_in_progress_downloads_tooltip": "{} descarga(s) en curso", + "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", + "info_in_progress_uploads_tooltip": "{} subida(s) en curso", + "info_completed_uploads_tooltip": "{} subida(s) completada(s)", + "receive_mode_downloads_dir": "Los archivos enviados a ti aparecen en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", + "gui_download_upload_progress_eta": "{0:s}, tiempo restante: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?", + "gui_quit_warning_quit": "Salir", + "gui_quit_warning_dont_quit": "Cancelar", + "gui_settings_whats_this": "¿Qué es esto?", + "gui_settings_autoupdate_timestamp": "Última comprobación: {}", + "gui_settings_autoupdate_timestamp_never": "Nunca", + "gui_settings_general_label": "Ajustes generales", + "gui_settings_sharing_label": "Configuración de uso compartido", + "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", + "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", + "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", + "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket", + "gui_settings_control_port_label": "Puerto de control", + "gui_settings_socket_file_label": "Archivo Socket", + "gui_settings_socks_label": "Puerto SOCKS", + "gui_settings_authenticate_label": "Configuración de autenticación Tor", + "gui_settings_authenticate_no_auth_option": "Sin autenticación, o autenticación por cookies", + "gui_settings_authenticate_password_option": "Contraseña", + "gui_settings_password_label": "Contraseña", + "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes", + "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes enchufables obfs4 incorporados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes enchufables obfs4 incorporados (requiere obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", + "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", + "gui_settings_button_save": "Guardar", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ayuda", + "gui_settings_shutdown_timeout_checkbox": "Usar temporizador de parada automática", + "gui_settings_shutdown_timeout": "Detener el compartir en:", + "history_in_progress_tooltip": "{} En progreso", + "history_completed_tooltip": "{} completado", + "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta del modo de recepción: {}", + "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.


    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", + "receive_mode_received_file": "Recibido: {}", + "gui_mode_share_button": "Compartir archivos", + "gui_mode_receive_button": "Recibir archivos", + "gui_settings_receiving_label": "Ajustes de recepción", + "gui_settings_downloads_label": "Guardar archivos en", + "gui_settings_downloads_button": "Examinar", + "gui_settings_public_mode_checkbox": "Modo público", + "systray_close_server_title": "Servidor OnionShare cerrado", + "systray_close_server_message": "Un usuario cerró el servidor", + "systray_page_loaded_title": "Página de OnionShare Cargada", + "systray_download_page_loaded_message": "Un usuario cargó la página de descarga", + "systray_upload_page_loaded_message": "Un usuario cargó la página de carga", + "gui_uploads": "Historial de carga", + "gui_no_uploads": "No hay subidas todavía", + "gui_clear_history": "Limpiar todo", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "El modo de recepción puede ser detenido por el remitente", + "gui_upload_in_progress": "Subida Iniciada {}", + "gui_upload_finished": "Subido {}", + "gui_download_in_progress": "Descarga iniciada {}", + "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", + "gui_settings_language_label": "Idioma preferido", + "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", + "gui_upload_finished_range": "Cargado {} a {}" } diff --git a/share/locale/fr.json b/share/locale/fr.json index ee206662..eecf1481 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,34 +1,158 @@ { - "preparing_files": "Préparation des fichiers à partager.", - "give_this_url": "Donnez cette URL à la personne qui doit recevoir le fichier :", - "ctrlc_to_stop": "Ctrl-C arrête le serveur", - "not_a_file": "{0:s} n'est pas un fichier.", - "other_page_loaded": "URL chargée", - "closing_automatically": "Fermeture automatique car le téléchargement est fini", + "preparing_files": "Compression des fichiers.", + "give_this_url": "Donnez cette adresse au destinataire :", + "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", + "not_a_file": "{0:s} n'est pas un fichier valide.", + "other_page_loaded": "Adresse chargée", + "closing_automatically": "Arrêt automatique car le téléchargement est fini", "systray_menu_exit": "Quitter", - "systray_download_started_title": "Téléchargement OnionShare Démarré", + "systray_download_started_title": "Téléchargement OnionShare démarré", "systray_download_started_message": "Un utilisateur télécharge vos fichiers", - "systray_download_completed_title": "Téléchargement OnionShare Complete", - "systray_download_canceled_title": "Téléchargement OnionShare Annulé", + "systray_download_completed_title": "Téléchargement OnionShare terminé", + "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", - "help_local_only": "Ne tentez pas d'utiliser Tor, uniquement pour développement", - "help_stay_open": "Laisser tourner le onion service après que le téléchargment soit fini", - "help_debug": "Enregistrer les erreurs sur le disque", + "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", + "help_stay_open": "Continuer le partage après le premier téléchargement", + "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", - "gui_share_start_server": "Démarrer le serveur", - "gui_share_stop_server": "Arrêter le serveur", - "gui_copy_url": "Copier URL", + "gui_share_start_server": "Commencer à partager", + "gui_share_stop_server": "Arrêter le partage", + "gui_copy_url": "Copier l'adresse", "gui_copy_hidservauth": "Copier HidServAuth", - "gui_downloads": "Téléchargements :", + "gui_downloads": "Historique de téléchargement", "gui_canceled": "Annulé", - "gui_copied_url": "URL copié dans le presse-papier", - "gui_please_wait": "Attendez-vous...", + "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", + "gui_please_wait": "Démarrage… Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", - "gui_quit_warning_dont_quit": "Ne quitter pas", + "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", - "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet" + "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", + "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", + "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", + "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", + "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", + "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", + "timeout_download_still_running": "En attente de la fin du téléchargement", + "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", + "gui_copied_hidservauth_title": "HidServAuth copié", + "gui_settings_window_title": "Paramètres", + "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", + "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", + "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", + "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", + "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", + "gui_settings_socket_file_label": "Fichier socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_no_auth_option": "Pas d'authentification ou authentification par cookie", + "gui_settings_authenticate_password_option": "Mot de passe", + "gui_settings_password_label": "Mot de passe", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de bridge", + "gui_settings_button_save": "Sauvegarder", + "gui_settings_button_cancel": "Annuler", + "gui_settings_button_help": "Aide", + "gui_settings_shutdown_timeout": "Arrêter le partage à :", + "connecting_to_tor": "Connexion au réseau Tor", + "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)", + "large_filesize": "Avertissement : envoyer un gros fichier peut prendre plusieurs heures", + "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "zip_progress_bar_format": "Compression : %p%", + "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0.", + "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", + "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", + "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", + "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)", + "systray_upload_started_title": "Envoi OnionShare démarré", + "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur", + "gui_no_downloads": "Pas encore de téléchargements", + "gui_copied_url_title": "Adresse OnionShare copiée", + "gui_quit_title": "Pas si vite", + "gui_share_quit_warning": "Vous êtes en train d'envoyer des fichiers. Voulez-vous vraiment quitter OnionShare ?", + "gui_receive_quit_warning": "Vous êtes en train de recevoir des fichiers. Voulez-vous vraiment quitter OnionShare ?", + "gui_settings_whats_this": "Qu'est-ce que c'est ?", + "gui_settings_autoupdate_label": "Rechercher des mises à jour", + "gui_settings_autoupdate_option": "Me notifier lorsque des mises à jour sont disponibles", + "gui_settings_general_label": "Paramètres généraux", + "gui_settings_sharing_label": "Paramètres de partage", + "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor inclue dans OnionShare", + "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le navigateur Tor", + "gui_settings_connection_type_test_button": "Tester la connexion à Tor", + "gui_settings_control_port_label": "Port de contrôle", + "gui_settings_authenticate_label": "Paramètres d'authentification de Tor", + "gui_settings_tor_bridges": "Support des bridges Tor", + "gui_settings_tor_bridges_custom_radio_option": "Utiliser des bridges personnalisés", + "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des bridges à l'adresse https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Aucun des bridges que vous avez ajouté ne fonctionne.\nVérifiez les à nouveau ou ajoutez-en d'autres.", + "settings_error_unknown": "Impossible de se connecter au contrôleur Tor car les paramètres n'ont pas de sens.", + "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Est-ce que le navigateur Tor (disponible sur torproject.org) fonctionne en arrière-plan ?", + "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", + "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier socket {}.", + "settings_error_auth": "Connecté à {}:{} mais impossible de s'authentifier. Peut-être que ce n'est pas un contrôleur Tor ?", + "settings_error_missing_password": "Connecté au contrôleur Tor mais il demande un mot de passe pour s'authentifier.", + "settings_error_unreadable_cookie_file": "Connecté au contrôleur Tor, mais le mot de passe est peut-être faux ou l'utilisateur n'a pas la permission de lire le fichier cookie.", + "settings_error_bundled_tor_not_supported": "L'utilisation de la version de Tor inclue avec OnionShare ne marche pas dans le mode développement sous Windows ou macOS.", + "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Peut-être qu'il n'y a pas de connexion à Internet ou que l'horloge système est inexacte ?", + "settings_error_bundled_tor_broken": "OnionShare n'a pas pu se connecter à Tor en arrière-plan :\n{}", + "error_tor_protocol_error": "Il y a eu une erreur avec Tor : {}", + "error_tor_protocol_error_unknown": "Il y a eu une erreur inconnue avec Tor", + "error_invalid_private_key": "Ce type de clé privée n'est pas supporté", + "update_available": "Une nouvelle version de OnionShare est disponible. Cliquez ici pour l'obtenir.

    Vous utilisez actuellement la version {} et la dernière version est la {}.", + "update_not_available": "Vous utilisez la dernière version d'OnionShare.", + "gui_tor_connection_ask_open_settings": "Oui", + "gui_tor_connection_ask_quit": "Quitter", + "gui_tor_connection_lost": "Déconnecté de Tor.", + "share_via_onionshare": "Partager via OnionShare", + "gui_save_private_key_checkbox": "Utiliser une adresse persistante (legacy)", + "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", + "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", + "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_url_label_stay_open": "Ce partage ne s'arrêtera pas automatiquement.", + "gui_url_label_onetime": "Ce partage s'arrêtera après le premier téléchargement complété.", + "gui_url_label_onetime_and_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants devraient réutiliser l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_status_indicator_share_stopped": "Prêt à partager", + "gui_status_indicator_share_working": "Démarrage…", + "gui_status_indicator_share_started": "Partage", + "gui_status_indicator_receive_stopped": "Prêt à recevoir", + "gui_status_indicator_receive_working": "Démarrage…", + "gui_status_indicator_receive_started": "Réception", + "gui_file_info": "{} fichiers, {}", + "gui_file_info_single": "{} fichier, {}", + "history_in_progress_tooltip": "{} en cours", + "history_completed_tooltip": "{} terminé", + "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", + "receive_mode_warning": "Avertissement : le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_received_file": "Reçu : {}", + "gui_mode_share_button": "Fichiers partagés", + "gui_mode_receive_button": "Fichiers reçus", + "gui_settings_receiving_label": "Paramètres de réception", + "gui_settings_downloads_label": "Enregistrer les fichiers sous", + "gui_settings_downloads_button": "Parcourir", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Le mode réception peut-être arrêté par l'expéditeur", + "gui_settings_public_mode_checkbox": "Mode public", + "systray_close_server_title": "Serveur OnionShare arrêté", + "gui_uploads": "Historique d'envoi", + "gui_no_uploads": "Pas encore d'envoi", + "gui_clear_history": "Tout effacer", + "gui_upload_in_progress": "Envoi démarré {}", + "gui_upload_finished_range": "Envoyé {} de {}", + "gui_upload_finished": "{} envoyé", + "gui_download_in_progress": "Téléchargement démarré {}", + "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", + "gui_settings_language_label": "Langue préférée", + "help_stealth": "Utilisation de l'autorisation client (avancé)", + "help_receive": "Recevoir des partages au lieu de les envoyer", + "gui_receive_start_server": "Démarrer le mode réception", + "gui_receive_stop_server": "Arrêter le mode réception", + "gui_receive_stop_server_shutdown_timeout": "Arrêter le mode réception ({}s restantes)", + "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", + "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", + "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", + "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", + "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", + "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)" } diff --git a/share/locale/ga.json b/share/locale/ga.json new file mode 100644 index 00000000..114661d2 --- /dev/null +++ b/share/locale/ga.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "Seirbhís onion á shocrú ar phort {0:d}.", + "preparing_files": "Comhaid á gcomhbhrú.", + "give_this_url": "Tabhair an seoladh seo don fhaighteoir:", + "give_this_url_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don fhaighteoir:", + "give_this_url_receive": "Tabhair an seoladh seo don seoltóir:", + "give_this_url_receive_stealth": "Tabhair an seoladh seo agus an líne HidServAuth seo don seoltóir:", + "ctrlc_to_stop": "Brúigh Ctrl+C chun stop a chur leis an bhfreastalaí", + "not_a_file": "Ní comhad bailí é {0:s}.", + "not_a_readable_file": "Ní comhad inléite é {0:s}.", + "no_available_port": "Níorbh fhéidir port a aimsiú chun an tseirbhís onion a thosú", + "other_page_loaded": "Seoladh lódáilte", + "close_on_timeout": "Cuireadh stop leis toisc go bhfuil an t-amadóir caite", + "closing_automatically": "Cuireadh stop leis toisc go bhfuil an íoslódáil críochnaithe", + "timeout_download_still_running": "Ag fanacht go gcríochnódh an íoslódáil", + "large_filesize": "Rabhadh: D'fhéadfadh go dtógfadh sé tamall fada comhad mór a sheoladh", + "systray_menu_exit": "Scoir", + "systray_download_started_title": "Tosaíodh Íoslódáil OnionShare", + "systray_download_started_message": "Thosaigh úsáideoir ag íoslódáil do chuid comhad", + "systray_download_completed_title": "Críochnaíodh Íoslódáil OnionShare", + "systray_download_completed_message": "Tá do chuid comhad íoslódáilte ag an úsáideoir", + "systray_download_canceled_title": "Cuireadh Íoslódáil OnionShare ar ceal", + "systray_download_canceled_message": "Chuir an t-úsáideoir an íoslódáil ar ceal", + "systray_upload_started_title": "Tosaíodh Uaslódáil OnionShare", + "systray_upload_started_message": "Thosaigh úsáideoir ag uaslódáil comhad go dtí do ríomhaire", + "help_local_only": "Ná húsáid Tor (tástáil amháin)", + "help_stay_open": "Lean ort ag comhroinnt tar éis an chéad íoslódáil", + "help_shutdown_timeout": "Stop ag comhroinnt tar éis líon áirithe soicindí", + "help_stealth": "Úsáid údarú cliaint (ardleibhéal)", + "help_receive": "Glac le comhaid chomhroinnte in áit iad a sheoladh", + "help_debug": "Déan tuairisc ar earráidí OnionShare ar stdout, agus earráidí Gréasáin ar an diosca", + "help_filename": "Liosta comhad nó fillteán le comhroinnt", + "help_config": "Suíomh saincheaptha don chomhad cumraíochta JSON (roghnach)", + "gui_drag_and_drop": "Tarraing agus scaoil comhaid agus fillteáin\nchun iad a chomhroinnt", + "gui_add": "Cuir Leis", + "gui_delete": "Scrios", + "gui_choose_items": "Roghnaigh", + "gui_share_start_server": "Comhroinn", + "gui_share_stop_server": "Stop ag comhroinnt", + "gui_share_stop_server_shutdown_timeout": "Stop ag Comhroinnt ({}s fágtha)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}", + "gui_receive_start_server": "Tosaigh an Mód Glactha", + "gui_receive_stop_server": "Stop an Mód Glactha", + "gui_receive_stop_server_shutdown_timeout": "Stop an Mód Glactha ({}s fágtha)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Amadóir uathstoptha caite {}", + "gui_copy_url": "Cóipeáil an Seoladh", + "gui_copy_hidservauth": "Cóipeáil HidServAuth", + "gui_downloads": "Stair Íoslódála", + "gui_no_downloads": "Níl aon rud íoslódáilte agat fós", + "gui_canceled": "Curtha ar ceal", + "gui_copied_url_title": "Cóipeáladh an Seoladh OnionShare", + "gui_copied_url": "Cóipeáladh an seoladh OnionShare go dtí an ghearrthaisce", + "gui_copied_hidservauth_title": "Cóipeáladh HidServAuth", + "gui_copied_hidservauth": "Cóipeáladh an líne HidServAuth go dtí an ghearrthaisce", + "gui_please_wait": "Ag tosú... Cliceáil lena chur ar ceal.", + "gui_download_upload_progress_complete": "%[%, {0:s} caite.", + "gui_download_upload_progress_starting": "{0:s}, %p% (á áireamh)", + "gui_download_upload_progress_eta": "{0:s}, am teachta measta: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Fan soic", + "gui_share_quit_warning": "Tá tú le linn roinnt comhad a sheoladh. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?", + "gui_receive_quit_warning": "Tá tú le linn roinnt comhad a íoslódáil. An bhfuil tú cinnte gur mhaith leat OnionShare a scor?", + "gui_quit_warning_quit": "Scoir", + "gui_quit_warning_dont_quit": "Cealaigh", + "error_rate_limit": "Rinne duine éigin an iomarca iarrachtaí míchearta ar do sheoladh, agus dá bharr sin stop OnionShare an freastalaí. Tosaigh ag comhroinnt arís agus cuir seoladh nua chuig an bhfaighteoir.", + "zip_progress_bar_format": "Á chomhbhrú: %p%", + "error_stealth_not_supported": "Chun údarú cliaint a úsáid, teastaíonn uait Tor 0.2.9.1-alpha (nó Brabhsálaí 6.5) agus python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Teastaíonn uait ar a laghad Tor 0.2.7.1 agus python3-stem 1.4.0 chun OnionShare a úsáid.", + "gui_settings_window_title": "Socruithe", + "gui_settings_whats_this": "Cad é seo", + "gui_settings_stealth_option": "Úsáid údarú cliaint (seanleagan)", + "gui_settings_stealth_hidservauth_string": "Toisc gur shábháil tú d'eochair phríobháideach, anois is féidir leat\ncliceáil chun an HidServAuth a chóipeáil.", + "gui_settings_autoupdate_label": "Lorg nuashonruithe", + "gui_settings_autoupdate_option": "Cuir in iúl dom nuair a bheidh leagan nua ar fáil", + "gui_settings_autoupdate_timestamp": "Seiceáilte: {}", + "gui_settings_autoupdate_timestamp_never": "Níor seiceáladh riamh", + "gui_settings_autoupdate_check_button": "Lorg Nuashonrú", + "gui_settings_general_label": "Socruithe ginearálta", + "gui_settings_sharing_label": "Socruithe comhroinnte", + "gui_settings_close_after_first_download_option": "Stop ag comhroinnt tar éis an chéad íoslódáil", + "gui_settings_connection_type_label": "Cén chaoi ar chóir do OnionShare ceangal le Tor?", + "gui_settings_connection_type_bundled_option": "Úsáid an leagan de Tor ionsuite in OnionShare", + "gui_settings_connection_type_automatic_option": "Déan cumraíocht uathoibríoch le Brabhsálaí Tor", + "gui_settings_connection_type_control_port_option": "Ceangal trí phort rialaithe", + "gui_settings_connection_type_socket_file_option": "Ceangal trí chomhad soicéid", + "gui_settings_connection_type_test_button": "Tástáil an Ceangal le Tor", + "gui_settings_control_port_label": "Port rialaithe", + "gui_settings_socket_file_label": "Comhad soicéid", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Socruithe fíordheimhnithe Tor", + "gui_settings_authenticate_no_auth_option": "Gan fíordheimhniú, nó fíordheimhniú le fianán", + "gui_settings_authenticate_password_option": "Focal faire", + "gui_settings_password_label": "Focal faire", + "gui_settings_tor_bridges": "Tacaíocht do dhroichid Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ná húsáid droichid", + "gui_settings_tor_bridges_obfs4_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite obfs4 (obfs4proxy de dhíth)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite(Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bain úsáid as córais iompair ionphlugáilte ionsuite meek_lite (Azure) (obfs4proxy de dhíth)", + "gui_settings_meek_lite_expensive_warning": "Rabhadh: Tá sé an-chostasach ar Thionscadal Tor na droichid meek_lite a chur ar fáil.

    Iarraimid ort gan iad a úsáid má tá tú in ann ceangal díreach a bhunú le Tor, nó trí chóras iompair obfs4, nó trí dhroichead eile.", + "gui_settings_tor_bridges_custom_radio_option": "Úsáid droichid shaincheaptha", + "gui_settings_tor_bridges_custom_label": "Is féidir leat droichid a fháil ó https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Níl aon cheann de na droichid ag obair.\nSeiceáil arís iad, nó bain triail as droichid eile.", + "gui_settings_button_save": "Sábháil", + "gui_settings_button_cancel": "Cealaigh", + "gui_settings_button_help": "Cabhair", + "gui_settings_shutdown_timeout_checkbox": "Úsáid amadóir uathstoptha", + "gui_settings_shutdown_timeout": "Stop ag comhroinnt ag:", + "settings_error_unknown": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor toisc nach féidir linn ciall a bhaint as na socruithe.", + "settings_error_automatic": "Níorbh fhéidir ceangal a bhunú leis an rialaitheoir Tor. An bhfuil Brabhsálaí Tor (ar fáil ó torproject.org) ag rith sa gcúlra?", + "settings_error_socket_port": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor ag {}:{}.", + "settings_error_socket_file": "Ní féidir ceangal a bhunú leis an rialaitheoir Tor trí chomhad soicéid {}.", + "settings_error_auth": "Ceangailte le {}:{}, ach ní féidir an ceangal a fhíordheimhniú. B'fhéidir nach rialaitheoir Tor é seo?", + "settings_error_missing_password": "Ceangailte le rialaitheoir Tor, ach teastaíonn focal faire uaidh.", + "settings_error_unreadable_cookie_file": "Ceangailte le rialaitheoir Tor, ach seans go bhfuil an focal faire mícheart, nó níl cead ag an úsáideoir an comhad ina bhfuil na fianáin a léamh.", + "settings_error_bundled_tor_not_supported": "Ní féidir an leagan de Tor a thagann le OnionShare a úsáid sa mód forbartha ar Windows nó ar macOS.", + "settings_error_bundled_tor_timeout": "An iomarca ama ag ceangal le Tor. B'fhéidir nach bhfuil ceangailte leis an Idirlíon, nó nach bhfuil clog do chórais socraithe mar is ceart?", + "settings_error_bundled_tor_broken": "Níorbh fhéidir le OnionShare ceangal le Tor sa gcúlra:\n{}", + "settings_test_success": "Ceangailte leis an rialaitheoir Tor.\n\nLeagan de Tor: {}\nTacaíonn sé le seirbhísí onion gearrshaolacha: {}.\nTacaíonn sé le fíordheimhniú cliaint: {}.\nTacaíonn sé le seoltaí .onion den chéad ghlúin eile: {}.", + "error_tor_protocol_error": "Tharla earráid le Tor: {}", + "error_tor_protocol_error_unknown": "Tharla earráid anaithnid le Tor", + "error_invalid_private_key": "Ní thacaítear le heochair phríobháideach den sórt seo", + "connecting_to_tor": "Ag ceangal le líonra Tor", + "update_available": "Leagan nua de OnionShare ar fáil. Cliceáil anseo lena íoslódáil.

    Tá {} agat agus is é {} an leagan is déanaí.", + "update_error_check_error": "Theip orainn nuashonruithe a lorg: Deir suíomh Gréasáin OnionShare gurb é '{}' an leagan is déanaí, leagan nach n-aithnímid…", + "update_error_invalid_latest_version": "Theip orainn nuashonruithe a lorg: B'fhéidir nach bhfuil ceangailte le Tor, nó nach bhfuil suíomh OnionShare ag obair faoi láthair?", + "update_not_available": "Tá an leagan is déanaí de OnionShare agat cheana.", + "gui_tor_connection_ask": "An bhfuil fonn ort na socruithe líonra a oscailt chun an fhadhb a réiteach?", + "gui_tor_connection_ask_open_settings": "Tá", + "gui_tor_connection_ask_quit": "Scoir", + "gui_tor_connection_error_settings": "Bain triail as na socruithe líonra a athrú chun ceangal le líonra Tor ó OnionShare.", + "gui_tor_connection_canceled": "Níorbh fhéidir ceangal a bhunú le Tor.\n\nDeimhnigh go bhfuil tú ceangailte leis an Idirlíon, ansin oscail OnionShare arís agus socraigh an ceangal le Tor.", + "gui_tor_connection_lost": "Dícheangailte ó Tor.", + "gui_server_started_after_timeout": "Bhí an t-amadóir uathstoptha caite sular thosaigh an freastalaí.\nCaithfidh tú comhroinnt nua a chruthú.", + "gui_server_timeout_expired": "Tá an t-amadóir uathstoptha caite cheana.\nCaithfidh tú é a athshocrú sular féidir leat comhaid a chomhroinnt.", + "share_via_onionshare": "Comhroinn trí OnionShare é", + "gui_use_legacy_v2_onions_checkbox": "Úsáid seoltaí sean-nóis", + "gui_save_private_key_checkbox": "Úsáid seoladh seasmhach (seanleagan)", + "gui_share_url_description": "Tá aon duine a bhfuil an seoladh OnionShare aige/aici in ann do chuid comhad a íoslódáil le Brabhsálaí Tor: ", + "gui_receive_url_description": "Tá aon duine a bhfuil an seoladh OnionShare aige/aici in ann comhaid a uaslódáil go dtí do ríomhaire le Brabhsálaí Tor: ", + "gui_url_label_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.

    Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)", + "gui_url_label_stay_open": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.", + "gui_url_label_onetime": "Stopfaidh an chomhroinnt seo nuair a chríochnóidh sé den chéad uair.", + "gui_url_label_onetime_and_persistent": "Ní stopfaidh an chomhroinnt seo go huathoibríoch.

    Úsáidfear an seoladh seo arís gach uair a dhéanfaidh tú comhroinnt. (Chun seoladh aon uaire a úsáid, múch \"Úsáid seoladh seasmhach\" sna socruithe.)", + "gui_status_indicator_share_stopped": "Réidh le comhroinnt", + "gui_status_indicator_share_working": "Á thosú…", + "gui_status_indicator_share_started": "Comhroinnt", + "gui_status_indicator_receive_stopped": "Réidh le glacadh le comhaid", + "gui_status_indicator_receive_working": "Á thosú…", + "gui_status_indicator_receive_started": "Glacadh", + "gui_file_info": "{} comhad, {}", + "gui_file_info_single": "{} chomhad, {}", + "history_in_progress_tooltip": "{} ar siúl", + "history_completed_tooltip": "{} críochnaithe", + "info_in_progress_uploads_tooltip": "{} uaslódáil ar siúl faoi láthair", + "info_completed_uploads_tooltip": "{} uaslódáil críochnaithe", + "error_cannot_create_downloads_dir": "Níorbh fhéidir fillteán a chruthú do chomhaid a nglacann tú leo: {}", + "receive_mode_downloads_dir": "Cuirfear comhaid a sheoltar chugat san fhillteán seo: {}", + "receive_mode_warning": "Rabhadh: Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire, fiú comhaid chontúirteacha a dhéanfadh dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.", + "gui_receive_mode_warning": "Sa mód glactha, beidh daoine in ann comhaid a uaslódáil ar do ríomhaire.

    Tá comhaid áirithe an-chontúirteach agus dhéanfaidís dochar do do ríomhaire dá n-osclófá iad. Ná hoscail ach comhaid ó dhaoine iontaofa mura bhfuil tú i do shaineolaí cruthanta slándála.", + "receive_mode_upload_starting": "Uaslódáil, méid iomlán {}, á tosú", + "receive_mode_received_file": "Faighte: {}", + "gui_mode_share_button": "Comhroinn Comhaid", + "gui_mode_receive_button": "Glac le Comhaid", + "gui_settings_receiving_label": "Socruithe glactha", + "gui_settings_downloads_label": "Sábháil comhaid i", + "gui_settings_downloads_button": "Brabhsáil", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Tá cead ag an seoltóir stop a chur leis an mód glactha", + "gui_settings_public_mode_checkbox": "Mód poiblí", + "systray_close_server_title": "Tá an freastalaí OnionShare dúnta", + "systray_close_server_message": "Dhún úsáideoir an freastalaí", + "systray_page_loaded_title": "Lódáladh leathanach OnionShare", + "systray_download_page_loaded_message": "Lódáil úsáideoir an leathanach íoslódála", + "systray_upload_page_loaded_message": "Lódáil úsáideoir an leathanach uaslódála", + "gui_uploads": "Stair Uaslódála", + "gui_no_uploads": "Níl aon rud uaslódáilte agat fós", + "gui_clear_history": "Glan Uile", + "gui_upload_in_progress": "Tosaíodh an Uaslódáil {}", + "gui_upload_finished_range": "Uaslódáladh {} go {}", + "gui_upload_finished": "Uaslódáladh {}", + "gui_download_in_progress": "Tosaíodh an Íoslódáil {}", + "gui_open_folder_error_nautilus": "Ní féidir an fillteán a oscailt toisc nach bhfuil nautilus ar fáil. Tá an comhad anseo: {}", + "gui_settings_language_label": "Do rogha teanga", + "gui_settings_language_changed_notice": "Atosaigh OnionShare chun an teanga nua a chur i bhfeidhm." +} diff --git a/share/locale/no.json b/share/locale/no.json index a04710d2..4d474cea 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,7 +1,190 @@ { - "give_this_url": "Gi personen du vil sende filen til denne URL-en:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", + "give_this_url": "Gi denne adressen til mottakeren:", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", "not_a_file": "{0:s} er ikke en fil.", - "gui_copied_url": "Kopierte URL-en til utklippstavlen", - "other_page_loaded": "En annen side har blitt lastet" + "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", + "other_page_loaded": "En annen side har blitt lastet", + "config_onion_service": "Setter opp løk-tjeneste på port {0:d}.", + "preparing_files": "Pakker sammen filer.", + "give_this_url_stealth": "Gi denne adressen og HidServAuth-linjen til mottakeren:", + "give_this_url_receive": "Gi denne adressen til avsenderen:", + "give_this_url_receive_stealth": "Gi denne adressen og HidServAuth-linjen til avsenderen:", + "not_a_readable_file": "{0:s} er ikke en lesbar fil.", + "no_available_port": "Fant ikke tilgjengelig port for oppstart av løktjenesten", + "close_on_timeout": "Stoppet fordi tidsavbruddsuret gikk ut", + "closing_automatically": "Stoppet fordi nedlasting fullførtes", + "timeout_download_still_running": "Venter på at nedlastingen skal fullføres", + "large_filesize": "Advarsel: forsendelse av stor deling kan ta timer", + "systray_menu_exit": "Avslutt", + "systray_download_started_title": "OnionShare-nedlasting startet", + "systray_download_started_message": "En bruker startet nedlasting av filene dine", + "systray_download_completed_title": "OnionShare-nedlasting fullført", + "systray_download_completed_message": "Brukeren fullførte nedlasting av filene dine", + "systray_download_canceled_title": "OnionShare-nedlasting avbrutt", + "systray_download_canceled_message": "Brukeren avbrøt nedlastingen", + "systray_upload_started_title": "OnionShare-opplasting startet", + "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin", + "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)", + "help_stay_open": "Fortsett å dele etter første nedlasting", + "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder", + "help_stealth": "Bruk klientidentifisering (avansert)", + "help_receive": "Motta delinger istedenfor å sende dem", + "help_debug": "Log OnionShare-feil til stdout, og vev-feil til disk", + "help_filename": "Liste over filer eller mapper å dele", + "help_config": "Egendefinert JSON-oppsettsfil (valgfri)", + "gui_drag_and_drop": "Dra og slipp filer og mapper\nfor å starte deling", + "gui_add": "Legg til", + "gui_delete": "Slett", + "gui_choose_items": "Velg", + "gui_share_start_server": "Start deling", + "gui_share_stop_server": "Stopp deling", + "gui_share_stop_server_shutdown_timeout": "Stopp deling ({}s gjenstår)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}", + "gui_receive_start_server": "Start mottaksmodus", + "gui_receive_stop_server": "Stopp mottaksmodus", + "gui_receive_stop_server_shutdown_timeout": "Stopp mottaksmodus ({}s gjenstår)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Tidsavbruddsuret går ut {}", + "gui_copy_url": "Kopier nettadresse", + "gui_copy_hidservauth": "Kopier HidServAuth", + "gui_downloads": "Nedlastingshistorikk", + "gui_no_downloads": "Ingen nedlastinger enda.", + "gui_canceled": "Avbrutt", + "gui_copied_url_title": "Kopierte OnionShare-adressen", + "gui_copied_hidservauth_title": "Kopierte HidServAuth-linje", + "gui_copied_hidservauth": "HidServAuth-linje kopiert til utklippstavle", + "gui_please_wait": "Starter… Klikk for å avbryte.", + "gui_download_upload_progress_complete": "%p%, {0:s} forløpt.", + "gui_download_upload_progress_starting": "{0:s}, %p% (regner ut)", + "gui_download_upload_progress_eta": "{0:s}, anslått ferdigstilt: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Hold an", + "gui_share_quit_warning": "Filer er i ferd med å bli sendt. Er du sikker på at du ønsker å avslutte OnionShare?", + "gui_receive_quit_warning": "Du har ikke fått overført alle filene til deg enda. Er du sikker på at du ønsker å avslutte OnionShare?", + "gui_quit_warning_quit": "Avslutt", + "gui_quit_warning_dont_quit": "Avbryt", + "error_rate_limit": "Noen har prøvd adressen din for mange ganger, som kan betyr at de prøver å gjette seg fram til den, OnionShare har derfor stoppet tjeneren. Start deling igjen, og send mottakeren en ny adresse å dele.", + "zip_progress_bar_format": "Pakker sammen: %p%", + "error_stealth_not_supported": "For å bruke klientidentitetsbekreftelse, trenger du minst Tor 0.2.9.1-alpha (eller Tor-nettleseren 6.5) og python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", + "gui_settings_window_title": "Innstillinger", + "gui_settings_whats_this": "Hva er dette?", + "gui_settings_stealth_option": "Bruk klientidentifisering (gammeldags)", + "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.", + "gui_settings_autoupdate_label": "Se etter ny versjon", + "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig", + "gui_settings_autoupdate_timestamp": "Sist sjekket: {}", + "gui_settings_autoupdate_timestamp_never": "Aldri", + "gui_settings_autoupdate_check_button": "Se etter ny versjon", + "gui_settings_general_label": "Hovedinnstillinger", + "gui_settings_sharing_label": "Delingsinnstillinger", + "gui_settings_close_after_first_download_option": "Stopp deling etter første nedlasting", + "gui_settings_connection_type_label": "Hvordan skal OnionShare koble seg til Tor?", + "gui_settings_connection_type_bundled_option": "Bruk Tor-versjonen som kommer med OnionShare", + "gui_settings_connection_type_automatic_option": "Forsøk automatisk oppsett med Tor-nettleser", + "gui_settings_connection_type_control_port_option": "Koble til ved bruk av kontrollport", + "gui_settings_connection_type_socket_file_option": "Koble til ved bruk av socket-fil", + "gui_settings_connection_type_test_button": "Test tilkobling til Tor", + "gui_settings_control_port_label": "Kontrollport", + "gui_settings_socket_file_label": "Socket-fil", + "gui_settings_socks_label": "SOCKS-port", + "gui_settings_authenticate_label": "Tor-identitetsbekreftelsesinnstillinger", + "gui_settings_authenticate_no_auth_option": "Ingen identitetsbekreftelse, eller kakeidentifiseringsbekreftelse", + "gui_settings_authenticate_password_option": "Passord", + "gui_settings_password_label": "Passord", + "gui_settings_tor_bridges": "Støtte for Tor-bro", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ikke benytt broer", + "gui_settings_tor_bridges_obfs4_radio_option": "Bruk innebygd pluggbare obfs4-transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare obfs4-transporter (krever obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Bruk innebygd pluggbare meek_lite (Azure) transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Bruk innebygd pluggbare meek_lite (Azure) transporter (krever obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advarsel: Meek-lite-broene er veldig kostbare å kjøre for Tor-prosjektet.

    Kun bruk dem hvis direkte tilkobling til Tor ikke virker, via obfs-transporter, eller andre normale broer.", + "gui_settings_tor_bridges_custom_radio_option": "Bruk egendefinerte broer", + "gui_settings_tor_bridges_custom_label": "Du kan hente broer fra https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ingen av broene du la til virker.\nDobbeltsjekk dem eller legg til andre.", + "gui_settings_button_save": "Lagre", + "gui_settings_button_cancel": "Avbryt", + "gui_settings_button_help": "Hjelp", + "gui_settings_shutdown_timeout_checkbox": "Bruk tidsavbruddsur", + "gui_settings_shutdown_timeout": "Stopp deling ved:", + "settings_saved": "Innstillinger lagret i {}", + "settings_error_unknown": "Kan ikke koble til Tor-kontroller fordi innstillingene dine ikke gir mening.", + "settings_error_automatic": "Kunne ikke koble til Tor-kontrolleren. Kjører Tor-nettleseren (tilgjengelig fra torproject.org) i bakgrunnen?", + "settings_error_socket_port": "Kan ikke koble til Tor-kontroller på {}:{}.", + "settings_error_socket_file": "Kan ikke koble til Tor-kontroller ved bruk av socket-fil {}.", + "settings_error_auth": "Tilkoblet til {}:{}, men kan ikke identitetsbekrefte. Kanskje dette ikke er en Tor-kontroller?", + "settings_error_missing_password": "Tilkoblet til Tor-kontroller, men den krever et passord for å identitetsbekrefte.", + "settings_error_unreadable_cookie_file": "Tilkoblet til Tor-kontrolleren, men passordet kan være galt, eller så har ikke brukeren din tilgang til å lese fra kakefilen.", + "settings_error_bundled_tor_not_supported": "Bruk av Tor-versjonen som kommer med OnionShare fungerer ikke i utviklermodus på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "Det tar for lang tid å koble til Tor. Kanskje du ikke er koblet til Internett, eller har du kanskje en unøyaktig systemklokke?", + "settings_error_bundled_tor_broken": "OnionShare kunne ikke koble til Tor i bakgrunnen:\n{}", + "settings_test_success": "Koblet til Tor-kontrolleren.\n\nTor-versjon: {}.\nStøtter flyktige løktjenester: {}.\nStøtter klientidentifisering: {}.\nStøtter nestegenerasjons .onion-adresser: {}.", + "error_tor_protocol_error": "Feil med Tor: {}", + "error_tor_protocol_error_unknown": "Ukjent feil med Tor", + "error_invalid_private_key": "Denne private nøkkeltypen er ikke støttet", + "connecting_to_tor": "Kobler til Tor-nettverket", + "update_available": "Ny OnionShare lansert. Klikk her for å hente det.

    Du bruker {} og nyeste versjon er {}.", + "update_error_check_error": "Kunne ikke sjekke etter nye versjoner: OnionShare-nettsiden sier at siste versjon er det ugjenkjennelige \"{}\"…", + "update_error_invalid_latest_version": "Kunne ikke sjekke etter ny versjon: Kanskje du ikke er koblet til Tor, eller kanskje OnionShare-nettsiden er nede?", + "update_not_available": "Du kjører siste OnionShare.", + "gui_tor_connection_ask": "Åpne innstillingene for å ordne opp i tilkoblingen til Tor?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_ask_quit": "Avslutt", + "gui_tor_connection_error_settings": "Prøv å endre hvordan OnionShare kobler til Tor-nettverket i innstillingene.", + "gui_tor_connection_canceled": "Kunne ikke koble til Tor.\n\nForsikre deg om at du er koblet til Internett, åpne så OnionShare igjen, og sett opp dets tilkobling til Tor.", + "gui_tor_connection_lost": "Frakoblet fra Tor.", + "gui_server_started_after_timeout": "Tidsavbruddsuret gikk ut før tjeneren startet.\nLag en ny deling.", + "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", + "share_via_onionshare": "OnionShare det", + "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", + "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (gammeldags)", + "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-nettleseren: ", + "gui_receive_url_description": "Alle som har denne OnionShare-adressen kan Laste opp filer til din datamaskin ved bruk av Tor-nettleseren: ", + "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", + "gui_url_label_stay_open": "Denne delingen vil ikke stoppe automatisk.", + "gui_url_label_onetime": "Denne delingen vil stoppe etter første fullføring.", + "gui_url_label_onetime_and_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For å bruke engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", + "gui_status_indicator_share_stopped": "Klar til å dele", + "gui_status_indicator_share_working": "Starter…", + "gui_status_indicator_share_started": "Deler", + "gui_status_indicator_receive_stopped": "Klar til mottak", + "gui_status_indicator_receive_working": "Starter…", + "gui_status_indicator_receive_started": "Mottar", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "info_in_progress_downloads_tooltip": "{} nedlasting(er) underveis", + "info_completed_downloads_tooltip": "{} nedlasting(er) fullført", + "info_in_progress_uploads_tooltip": "{} opplasting(er) underveis", + "info_completed_uploads_tooltip": "{} nedlasting(er) fullført", + "error_cannot_create_downloads_dir": "Kunne ikke opprette mottaksmodusmappe: {}", + "error_downloads_dir_not_writable": "Mottaksmodusmappen er skrivebeskyttet: {}", + "receive_mode_downloads_dir": "Filer sendt til deg vil vises i denne mappen: {}", + "receive_mode_warning": "Advarsel: Mottaksmodus lar folk laste opp filer til din datamaskin. Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", + "gui_receive_mode_warning": "Mottaksmodus lar folk laste opp filer til din datamaskin.

    Noen filer kan potensielt ta over datamaskinen din hvis du åpner dem. Kun åpne ting fra folk du stoler på, eller hvis du vet hva du gjør.", + "receive_mode_upload_starting": "Opplasting av total størrelse {} starter", + "receive_mode_received_file": "Mottatt: {}", + "gui_mode_share_button": "Del filer", + "gui_mode_receive_button": "Motta filer", + "gui_settings_receiving_label": "Mottaksinnstillinger", + "gui_settings_downloads_label": "Lagre filer i", + "gui_settings_downloads_button": "Utforstk", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "Mottaksmodus kan stoppes av avsenderen", + "gui_settings_public_mode_checkbox": "Offentlig modus", + "systray_close_server_title": "OnionShare-tjener lukket", + "systray_close_server_message": "En bruker stengte tjeneren", + "systray_page_loaded_title": "OnionShare-side lastet", + "systray_download_page_loaded_message": "En bruker lastet inn nedlastingssiden", + "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden", + "gui_uploads": "Opplastingshistorikk", + "gui_no_uploads": "Ingen opplastinger enda.", + "gui_clear_history": "Tøm alt", + "gui_upload_in_progress": "Opplasting startet {}", + "gui_upload_finished_range": "Lastet opp {} til {}", + "gui_upload_finished": "Lastet opp {}", + "gui_open_folder_error_nautilus": "Kan ikke åpne mappe fordi nautilus ikke er tilgjengelig. Filen er her: {}", + "history_in_progress_tooltip": "{} underveis", + "history_completed_tooltip": "{} fullført", + "gui_download_in_progress": "Nedlasting startet {}", + "gui_settings_language_label": "Foretrukket språk", + "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg.", + "timeout_upload_still_running": "Venter på at opplastingen fullføres" } -- cgit v1.2.3-54-g00ecf From ffd1a76b5c056a9f7b6608d25576e4e7a6dd411d Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 11 Dec 2018 13:20:40 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 97 +++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index 6a89d825..9ad705a3 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -1,5 +1,5 @@ { - "config_onion_service": "Configurando o serviço onion na entrada {0:d}", + "config_onion_service": "Configurando o serviço onion na porta {0:d}.", "preparing_files": "Comprimindo arquivos.", "give_this_url": "Dar este endereço ao destinatário:", "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", @@ -55,7 +55,7 @@ "gui_please_wait": "Começando...Clique para cancelar.", "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_download_upload_progress_eta": "{0:s}, tempo estimado para término: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Mais devagar", "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?", @@ -109,9 +109,9 @@ "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?", "settings_error_socket_port": "Não pode ligar ao controlador do Tor em {}:{}.", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", + "settings_error_socket_file": "Não foi possível conectar ao controlador Tor usando o arquivo de socket {}.", + "settings_error_auth": "Conectado a {}:{}, mas não foi possível autenticar. Talvez este não seja um controlador Tor?", + "settings_error_missing_password": "Conectado ao controlador Tor, mas é preciso ter uma senha para autenticar.", "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.", "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", @@ -122,26 +122,26 @@ "error_invalid_private_key": "Este tipo de chave privada não possui suporte", "connecting_to_tor": "Conectando à rede Tor", "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", + "update_error_check_error": "Não foi possível verificar por novas versões: o website do OnionShare está dizendo que a última versão é a irreconhecível '{}'…", + "update_error_invalid_latest_version": "Não foi possível verificar por uma nova versão: talvez você não esteja conectada ao Tor, ou talvez o website do OnionShare esteja fora do ar?", "update_not_available": "Você está rodando a última versão de OnionShare.", - "gui_tor_connection_ask": "", + "gui_tor_connection_ask": "Abrir as configurações para consertar a conexão ao Tor?", "gui_tor_connection_ask_open_settings": "Sim", "gui_tor_connection_ask_quit": "Sair", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", + "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.", + "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", "gui_tor_connection_lost": "Desconectado do Tor.", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", + "gui_server_started_after_timeout": "O tempo se esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", + "gui_server_timeout_expired": "O temporizador já se esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", "share_via_onionshare": "Compartilhar por meio de OnionShare", - "gui_use_legacy_v2_onions_checkbox": "", + "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", + "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", + "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode fazer upload de arquivos para o seu computador usando o Tor Browser: ", + "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_url_label_stay_open": "Este compartilhamento não sera encerrado automaticamente.", + "gui_url_label_onetime": "Este compartilhamento será encerrado depois da primeira vez que for utilizado.", + "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desligue a opção \"Usar endereço persistente\" nas configurações.)", "gui_status_indicator_share_stopped": "Pronto para compartilhar", "gui_status_indicator_share_working": "Começando…", "gui_status_indicator_share_started": "Compartilhando", @@ -150,36 +150,37 @@ "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", "gui_file_info_single": "{} ficheiro, {}", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", + "history_in_progress_tooltip": "{} em progresso", + "history_completed_tooltip": "{} completado", + "info_in_progress_uploads_tooltip": "{} upload(s) em progresso", + "info_completed_uploads_tooltip": "{} upload(s) completado(s)", + "error_cannot_create_downloads_dir": "Não foi possível a pasta do modo de recepção: {}", + "receive_mode_downloads_dir": "Os arquivos enviados para você aparecem na seguinte pasta: {}", + "receive_mode_warning": "Atenção: O modo de recepção permite que as pessoas enviem arquivos para o seu computador. Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", + "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.

    Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", + "receive_mode_upload_starting": "Um upload de tamanho total {} está sendo iniciado", + "receive_mode_received_file": "Recebido: {}", + "gui_mode_share_button": "Compartilhar Arquivos", + "gui_mode_receive_button": "Receber Arquivos", + "gui_settings_receiving_label": "Configurações de recepção", + "gui_settings_downloads_label": "Armazenar arquivos em", "gui_settings_downloads_button": "", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", + "gui_settings_public_mode_checkbox": "Modo público", + "systray_close_server_title": "Servidor OnionShare encerrado", + "systray_close_server_message": "Um usuário encerrou o servidor", + "systray_page_loaded_title": "Página OnionShare Carregada", + "systray_download_page_loaded_message": "Um usuário carregou a página de download", + "systray_upload_page_loaded_message": "Um usuário carregou a página de upload", + "gui_uploads": "Histórico de Uploads", + "gui_no_uploads": "Nenhum upload realizado", + "gui_clear_history": "Limpar Tudo", + "gui_upload_in_progress": "Upload Iniciado {}", + "gui_upload_finished_range": "Upload de {} feito para {}", "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_download_in_progress": "Download Iniciado {}", + "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", + "gui_settings_language_label": "Idioma preferido", + "gui_settings_language_changed_notice": "Reinicie o OnionShare para que sua alteração de idioma tenha efeito.", + "timeout_upload_still_running": "Esperando o término do upload" } -- cgit v1.2.3-54-g00ecf From d853cb8aa359405ef67e65e921a17422258d4cde Mon Sep 17 00:00:00 2001 From: emma peel Date: Tue, 11 Dec 2018 15:05:01 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index c2d89282..131fb9be 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -98,8 +98,8 @@ "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor, haz una nueva porción.", "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", "share_via_onionshare": "Compártelo con OnionShare", - "gui_use_legacy_v2_onions_checkbox": "Usar direcciones de legado", - "gui_save_private_key_checkbox": "Usar una dirección persistente (legado)", + "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", + "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Tor Browser: ", "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Tor Browser: ", "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", -- cgit v1.2.3-54-g00ecf From dfca32ad8295e473bdba0badaa390de5a40cb3eb Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 11 Dec 2018 23:17:41 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index 9ad705a3..abe7c795 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -135,7 +135,7 @@ "gui_server_timeout_expired": "O temporizador já se esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", "share_via_onionshare": "Compartilhar por meio de OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", - "gui_save_private_key_checkbox": "", + "gui_save_private_key_checkbox": "Usar um endereço persistente (modo antigo)", "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode fazer upload de arquivos para o seu computador usando o Tor Browser: ", "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desligue a opção \"Usar endereço persistente\" nas configurações.)", -- cgit v1.2.3-54-g00ecf From 6cc85d499569c3dceaa6ef557a4ae0b4b8b288dd Mon Sep 17 00:00:00 2001 From: xin Date: Wed, 12 Dec 2018 11:15:49 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index eecf1481..a5ed1710 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -154,5 +154,22 @@ "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", - "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)" + "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)", + "timeout_upload_still_running": "En attente de la fin de l'envoi", + "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée, vous pouvez maintenant\ncliquer pour copier votre HidServAuth.", + "gui_settings_autoupdate_check_button": "Vérifier les mises à jour", + "settings_test_success": "Connecté au contrôleur Tor.\n\nVersion de Tor : {}\nSupport des services oignon éphémères : {}.\nSupport de l'authentification client : {}.\nSupport de la nouvelle génération d'adresses .onion : {}.", + "update_error_check_error": "Impossible de vérifier les mises à jour : le site web d'OnionShare dit que la dernière version est la méconnaissable '{}'…", + "update_error_invalid_latest_version": "Impossible de vérifier les mises à jour : peut-être qu'il n'y a pas de connexion à Tor ou que le site web d'OnionShare est hors service ?", + "gui_tor_connection_ask": "Ouvrir les paramètres pour configurer la connexion à Tor ?", + "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nVérifiez votre connexion à Internet, puis réouvrez OnionShare et configurez sa connexion à Tor.", + "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses legacy", + "info_in_progress_uploads_tooltip": "{} envoi(s) en cours", + "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)", + "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", + "receive_mode_upload_starting": "Un envoi d'une taille totale de {} a commencé", + "systray_close_server_message": "Une personne a arrêté le serveur", + "systray_page_loaded_title": "Page OnionShare chargée", + "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", + "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi" } -- cgit v1.2.3-54-g00ecf From ba0d86a7affc9cc32cc4fd73e25ae7a846aba074 Mon Sep 17 00:00:00 2001 From: emma peel Date: Wed, 12 Dec 2018 11:32:54 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index a5ed1710..2606917f 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -171,5 +171,6 @@ "systray_close_server_message": "Une personne a arrêté le serveur", "systray_page_loaded_title": "Page OnionShare chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", - "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi" + "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine à {}." } -- cgit v1.2.3-54-g00ecf From 12edae103a2a06ad1e8108d5c34380f82a961429 Mon Sep 17 00:00:00 2001 From: xin Date: Wed, 12 Dec 2018 11:33:03 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 2606917f..01c152d8 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -172,5 +172,5 @@ "systray_page_loaded_title": "Page OnionShare chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", - "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine à {}." + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}." } -- cgit v1.2.3-54-g00ecf From c6cb0c9bbd870575c599e2625a47b432a33f6e8b Mon Sep 17 00:00:00 2001 From: xin Date: Wed, 12 Dec 2018 11:34:33 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 01c152d8..8e89ec3f 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -172,5 +172,14 @@ "systray_page_loaded_title": "Page OnionShare chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", - "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}." + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_settings_tor_bridges_obfs4_radio_option": "Utiliser les transports enfichables obfs4 intégrés", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (nécessitent obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (nécessitent obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Avertissement : les bridges meek_lite sont très coûteux à faire fonctionner pour le Tor Project.

    Utilisez les seulement si vous ne pouvez pas vous connecter à Tor directement, via les transports obfs4 ou avec d'autres bridges normaux.", + "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", + "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", + "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage." } -- cgit v1.2.3-54-g00ecf From e417423493c95e0847e146a15c8eb9c229ae75d7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Wed, 12 Dec 2018 11:34:52 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 8e89ec3f..bf1cb85d 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -181,5 +181,6 @@ "gui_settings_meek_lite_expensive_warning": "Avertissement : les bridges meek_lite sont très coûteux à faire fonctionner pour le Tor Project.

    Utilisez les seulement si vous ne pouvez pas vous connecter à Tor directement, via les transports obfs4 ou avec d'autres bridges normaux.", "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", - "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage." + "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique s'est épuisée." } -- cgit v1.2.3-54-g00ecf From 540075805582ba31b8f9887bb5b637183974deb2 Mon Sep 17 00:00:00 2001 From: xin Date: Wed, 12 Dec 2018 11:43:57 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index bf1cb85d..c9cc2ffa 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -182,5 +182,5 @@ "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique s'est épuisée." + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique est épuisée" } -- cgit v1.2.3-54-g00ecf From e827a6dc5be077dc3766f8589f1245fc43deeb00 Mon Sep 17 00:00:00 2001 From: xin Date: Wed, 12 Dec 2018 11:44:43 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index c9cc2ffa..38580eb1 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -7,15 +7,15 @@ "closing_automatically": "Arrêt automatique car le téléchargement est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare démarré", - "systray_download_started_message": "Un utilisateur télécharge vos fichiers", + "systray_download_started_message": "Une personne télécharge vos fichiers", "systray_download_completed_title": "Téléchargement OnionShare terminé", "systray_download_canceled_title": "Téléchargement OnionShare annulé", - "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", + "systray_download_canceled_message": "La personne a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Continuer le partage après le premier téléchargement", "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", - "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", + "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", @@ -37,12 +37,12 @@ "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", "timeout_download_still_running": "En attente de la fin du téléchargement", - "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", + "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers", "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", - "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", + "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", "gui_settings_socket_file_label": "Fichier socket", @@ -68,7 +68,7 @@ "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)", "systray_upload_started_title": "Envoi OnionShare démarré", "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur", - "gui_no_downloads": "Pas encore de téléchargements", + "gui_no_downloads": "Pas encore de téléchargement", "gui_copied_url_title": "Adresse OnionShare copiée", "gui_quit_title": "Pas si vite", "gui_share_quit_warning": "Vous êtes en train d'envoyer des fichiers. Voulez-vous vraiment quitter OnionShare ?", @@ -182,5 +182,5 @@ "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique est épuisée" + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré" } -- cgit v1.2.3-54-g00ecf From d8246ded81f1ea8f661ff98df65f267cab18a6b5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 20:51:07 -0800 Subject: Fix test_load_strings_loads_other_languages test --- tests/test_onionshare_strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_onionshare_strings.py b/tests/test_onionshare_strings.py index ea57e3a9..3ac22a11 100644 --- a/tests/test_onionshare_strings.py +++ b/tests/test_onionshare_strings.py @@ -51,7 +51,7 @@ class TestLoadStrings: common_obj.settings = Settings(common_obj) common_obj.settings.set('locale', 'fr') strings.load_strings(common_obj) - assert strings._('preparing_files') == "Préparation des fichiers à partager." + assert strings._('preparing_files') == "Compression des fichiers." def test_load_invalid_locale( self, common_obj, locale_invalid, sys_onionshare_dev_mode): -- cgit v1.2.3-54-g00ecf From a0e0b71da4d08f645d133caf4e4f4d8f12e68d4a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 20:58:13 -0800 Subject: Remove "(legacy) from v2-only settings dialog options" --- share/locale/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/en.json b/share/locale/en.json index c38bfdff..43c7cfe3 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -69,7 +69,7 @@ "error_ephemeral_not_supported": "OnionShare requires at least both Tor 0.2.7.1 and python3-stem 1.4.0.", "gui_settings_window_title": "Settings", "gui_settings_whats_this": "What's this?", - "gui_settings_stealth_option": "Use client authorization (legacy)", + "gui_settings_stealth_option": "Use client authorization", "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now\nclick to copy your HidServAuth.", "gui_settings_autoupdate_label": "Check for new version", "gui_settings_autoupdate_option": "Notify me when a new version is available", @@ -137,7 +137,7 @@ "share_via_onionshare": "OnionShare it", "gui_connect_to_tor_for_onion_settings": "Connect to Tor to see onion service settings", "gui_use_legacy_v2_onions_checkbox": "Use legacy addresses", - "gui_save_private_key_checkbox": "Use a persistent address (legacy)", + "gui_save_private_key_checkbox": "Use a persistent address", "gui_share_url_description": "Anyone with this OnionShare address can download your files using the Tor Browser: ", "gui_receive_url_description": "Anyone with this OnionShare address can upload files to your computer using the Tor Browser: ", "gui_url_label_persistent": "This share will not auto-stop.

    Every subsequent share reuses the address. (To use one-time addresses, turn off \"Use persistent address\" in the settings.)", -- cgit v1.2.3-54-g00ecf From f9e6e6964cdc34b685ac1a0d6a7e82540b654a31 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 21:07:23 -0800 Subject: Remove unnecessary imports from settings dialog tests --- tests/local_onionshare_settings_dialog_legacy_tor_test.py | 3 --- tests/local_onionshare_settings_dialog_no_tor_test.py | 3 --- tests/local_onionshare_settings_dialog_v3_tor_test.py | 2 -- 3 files changed, 8 deletions(-) diff --git a/tests/local_onionshare_settings_dialog_legacy_tor_test.py b/tests/local_onionshare_settings_dialog_legacy_tor_test.py index d08362f1..ae6ce272 100644 --- a/tests/local_onionshare_settings_dialog_legacy_tor_test.py +++ b/tests/local_onionshare_settings_dialog_legacy_tor_test.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -import json import unittest -import time -from PyQt5 import QtCore, QtTest from onionshare import strings from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py index a10c2f2e..9519f77e 100644 --- a/tests/local_onionshare_settings_dialog_no_tor_test.py +++ b/tests/local_onionshare_settings_dialog_no_tor_test.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -import json import unittest -import time -from PyQt5 import QtCore, QtTest from onionshare import strings from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub diff --git a/tests/local_onionshare_settings_dialog_v3_tor_test.py b/tests/local_onionshare_settings_dialog_v3_tor_test.py index 815b2f72..cb10f8c9 100644 --- a/tests/local_onionshare_settings_dialog_v3_tor_test.py +++ b/tests/local_onionshare_settings_dialog_v3_tor_test.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import json import unittest -from PyQt5 import QtCore, QtTest from onionshare import strings from .SettingsGuiBaseTest import SettingsGuiBaseTest, OnionStub -- cgit v1.2.3-54-g00ecf From dc24b5ecd37cf9559dc6efd9dccb8a4cb52354ba Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 21:08:51 -0800 Subject: Fix bug in OnionStub, the stub used in settings dialog tests --- tests/SettingsGuiBaseTest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 57626d1d..aea19c68 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -14,7 +14,7 @@ from onionshare_gui.settings_dialog import SettingsDialog class OnionStub(object): def __init__(self, is_authenticated, supports_v3_onions=False): self._is_authenticated = is_authenticated - self.supports_v3_onions = True + self.supports_v3_onions = supports_v3_onions def is_authenticated(self): return self._is_authenticated -- cgit v1.2.3-54-g00ecf From 7f335efaa3bca15d8cfb59cdda17fe1a0059db1f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 21:15:18 -0800 Subject: Always pass in both is_authenticated and supports_v3_onions to OnionStub --- tests/SettingsGuiBaseTest.py | 2 +- tests/local_onionshare_settings_dialog_no_tor_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index aea19c68..00056219 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -12,7 +12,7 @@ from onionshare_gui.settings_dialog import SettingsDialog class OnionStub(object): - def __init__(self, is_authenticated, supports_v3_onions=False): + def __init__(self, is_authenticated, supports_v3_onions): self._is_authenticated = is_authenticated self.supports_v3_onions = supports_v3_onions diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py index 9519f77e..f01e049d 100644 --- a/tests/local_onionshare_settings_dialog_no_tor_test.py +++ b/tests/local_onionshare_settings_dialog_no_tor_test.py @@ -15,7 +15,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): SettingsGuiBaseTest.tear_down() def test_gui_no_tor(self): - self.gui.onion = OnionStub(False) + self.gui.onion = OnionStub(False, False) self.gui.reload_settings() self.run_settings_gui_tests() -- cgit v1.2.3-54-g00ecf From bf337b5e34aa3a160938acaf6a2c4f1bcba0db27 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 13 Dec 2018 21:44:47 -0800 Subject: Remove qtapp.processEvents() call from settings dialog tests, because they cause a segfault in circleci for some reason --- tests/SettingsGuiBaseTest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 00056219..47d698f3 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -63,7 +63,6 @@ class SettingsGuiBaseTest(object): def run_settings_gui_tests(self): self.gui.show() - self.gui.qtapp.processEvents() # Window is shown self.assertTrue(self.gui.isVisible()) -- cgit v1.2.3-54-g00ecf From 9b4cf87f390ec611c1de0c0cd0292583ce9f90f6 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 08:26:34 +0000 Subject: Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ --- share/locale/el.json | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/share/locale/el.json b/share/locale/el.json index a67a5f75..77f0b85b 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -1,31 +1,31 @@ { "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", + "preparing_files": "Συμπίεση αρχείων.", + "give_this_url": "Δώσε αυτή την διεύθυνση στον/στην παραλήπτη/τρια:", "give_this_url_stealth": "", - "give_this_url_receive": "", + "give_this_url_receive": "Δώσε αυτή τη διεύθυνση στον/στην αποστολέα:", "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", + "ctrlc_to_stop": "Πάτα Ctrl+C για να σταματήσεις το σέρβερ", + "not_a_file": "{0:s} δεν είναι έγκυρο αρχείο.", + "not_a_readable_file": "Το {0:s} δεν είναι αναγνώσιμο αρχείο.", + "no_available_port": "Δεν βρέθηκε διαθέσιμη θύρα για να ξεκινήσει η υπηρεσία onion", + "other_page_loaded": "Η διεύθυνση φορτώθηκε", + "close_on_timeout": "Τερματίστηκε γιατί το χρονόμετρο τερματισμού έφτασε στο τέλος", + "closing_automatically": "Τερματίστηκε επειδή η λήψη ολοκληρώθηκε", + "timeout_download_still_running": "Αναμονή ολοκλήρωσης της λήψης", + "large_filesize": "Προειδοποίηση: Η αποστολή μεγάλου όγκου δεδομένων μπορεί να διαρκέσει ώρες", + "systray_menu_exit": "Έξοδος", + "systray_download_started_title": "Η λήψη του OnionShare ξεκίνησε", + "systray_download_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να κατεβάζει τα αρχεία σου", + "systray_download_completed_title": "Η λήψη του OnionShare ολοκληρώθηκε", + "systray_download_completed_message": "Ο/η χρήστης/τρια ολοκλήρωσε την λήψη των αρχείων σου", + "systray_download_canceled_title": "Η λήψη του OnionShare ακυρώθηκε", + "systray_download_canceled_message": "Ο/η χρήστης/τρια ακύρωσε τη λήψη", + "systray_upload_started_title": "Η λήψη του OnionShare ξεκίνησε", + "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου", + "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", + "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά τη πρώτη λήψη", + "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", "help_stealth": "", "help_receive": "", "help_debug": "", @@ -181,5 +181,6 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "Αναμονή ολοκλήρωση του ανεβάσματος" } -- cgit v1.2.3-54-g00ecf From 9ddca486f863cdc9eb9dc4bd08386e1489af4e64 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 09:07:15 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index a67a5f75..9b15a20d 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "خروج", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -32,9 +32,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "افزودن", + "gui_delete": "Delete", + "gui_choose_items": "گزینش", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "خروج", + "gui_quit_warning_dont_quit": "لغو", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "تنظیمات", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "هرگز", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "تنظیمات عمومی", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -89,8 +89,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,8 +101,8 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", + "gui_settings_button_save": "ذخیره", + "gui_settings_button_cancel": "لغو", "gui_settings_button_help": "", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "بله", + "gui_tor_connection_ask_quit": "خروج", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -147,7 +147,7 @@ "gui_status_indicator_share_started": "", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "درحال دریافت", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "فهرست", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", @@ -180,6 +180,6 @@ "gui_upload_finished": "", "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", + "gui_settings_language_label": "زبان ترجیحی", "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 0ab18fd7ef1fb943abf09a2034fd743f7655a7ad Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 15:21:28 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 7aaacd9e..94dca8fd 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -32,7 +32,7 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", + "gui_add": "إضافة", "gui_delete": "حذف", "gui_choose_items": "إختر", "gui_share_start_server": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "خروج", + "gui_quit_warning_dont_quit": "إلغاء", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "الإعدادات", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "التحقق من الإصدار الجديد", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "أبدا", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "الإعدادات العامة", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -89,8 +89,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "كلمة السر", + "gui_settings_password_label": "كلمة السر", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "حفظ", + "gui_settings_button_cancel": "إلغاء", + "gui_settings_button_help": "مساعدة", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "نعم,", + "gui_tor_connection_ask_quit": "خروج", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -147,7 +147,7 @@ "gui_status_indicator_share_started": "", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "جاري الإستلام", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "استعراض", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From ca24a4d07b11ec0f84fae97870ea4d8a19eb7cb6 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 15:23:53 +0000 Subject: Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ --- share/locale/bn.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/share/locale/bn.json b/share/locale/bn.json index 25cd5c48..e62db7da 100644 --- a/share/locale/bn.json +++ b/share/locale/bn.json @@ -15,7 +15,7 @@ "timeout_download_still_running": "", "timeout_upload_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "প্রস্থান করুন", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,9 +33,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "সংযোজন করুন", + "gui_delete": "মুছ", + "gui_choose_items": "পছন্দ করুন", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -61,13 +61,13 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "প্রস্থান করুন", + "gui_quit_warning_dont_quit": "বাতিল", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "সেটিং", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", @@ -85,13 +85,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "নিয়ন্ত্রন পোর্ট", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "পাসওয়ার্ড", + "gui_settings_password_label": "পাসওয়ার্ড", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -102,9 +102,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "সেভ", + "gui_settings_button_cancel": "বাতিল", + "gui_settings_button_help": "সাহায্য", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -127,8 +127,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "হ্যাঁ", + "gui_tor_connection_ask_quit": "প্রস্থান করুন", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -165,7 +165,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "দেখা", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", "systray_close_server_message": "", -- cgit v1.2.3-54-g00ecf From dee9f5cb29329c5e5fb6e4f02124c5c954290f5b Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 15:28:00 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ --- share/locale/zh_Hans.json | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index a67a5f75..300fa7ac 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "退出", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -32,9 +32,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "添加", + "gui_delete": "删除", + "gui_choose_items": "选择", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "退出", + "gui_quit_warning_dont_quit": "取消", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "设置中", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "检查新版本", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "从不", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "常规设置", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "控制端口", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "密码", + "gui_settings_password_label": "密码", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "保存", + "gui_settings_button_cancel": "取消", + "gui_settings_button_help": "帮助", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "是的", + "gui_tor_connection_ask_quit": "退出", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "分享", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "正在接收", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "分享轨迹", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", @@ -174,12 +174,12 @@ "systray_upload_page_loaded_message": "", "gui_uploads": "", "gui_no_uploads": "", - "gui_clear_history": "", + "gui_clear_history": "清除所有", "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", + "gui_settings_language_label": "首选语言", "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 6b7d9b8a98c9fbf054d1e4412aef1172ded10484 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 15:23:32 +0000 Subject: Translated using Weblate (Gujarati) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/gu/ --- share/locale/gu.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/gu.json diff --git a/share/locale/gu.json b/share/locale/gu.json new file mode 100644 index 00000000..8ebfc5fb --- /dev/null +++ b/share/locale/gu.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "ક્યારેય નહીં", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 491b28c9e6dd68e5fe6b7a8f3c4a868733dcbfc6 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:28:46 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 94dca8fd..a2313ac1 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "ألغى", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From 31d1fe1e1a74d3e742811c5ce620c8a152ada4e8 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:18:27 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ --- share/locale/zh_Hans.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index 300fa7ac..958bfb3c 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "取消", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From c2105feddb05052d97ab5e45746c126b39e7fac9 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:28:24 +0000 Subject: Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ --- share/locale/cs.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index a595ce67..74eb8ea3 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -3,7 +3,7 @@ "preparing_files": "Připravuji soubory ke sdílení.", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", + "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", @@ -40,8 +40,8 @@ "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", - "gui_settings_connection_type_control_port_option": "Connect using control port", - "gui_settings_connection_type_socket_file_option": "Connect using socket file", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_authenticate_label": "Autentizační možnosti Toru", -- cgit v1.2.3-54-g00ecf From 8fe808a15285efd6ad365c18f55b85039d638c4a Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:24:30 +0000 Subject: Translated using Weblate (Esperanto) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/eo/ --- share/locale/eo.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/share/locale/eo.json b/share/locale/eo.json index 9902e4ae..bf578276 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -3,9 +3,9 @@ "preparing_files": "Preparas dosierojn por kundivido.", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", + "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", "not_a_file": "{0:s} ne estas dosiero.", - "other_page_loaded": "URL loaded", + "other_page_loaded": "", "closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis", "large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn", "help_local_only": "Ne strebu uzi tor: nur por evoluado", @@ -37,7 +37,7 @@ "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", - "gui_settings_window_title": "Settings", + "gui_settings_window_title": "", "gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?", "gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser", "gui_settings_connection_type_control_port_option": "Konekti per kontrolpordo", @@ -45,7 +45,7 @@ "gui_settings_control_port_label": "Kontrolpordo", "gui_settings_socket_file_label": "Socket-dosiero", "gui_settings_authenticate_label": "Tor authentication options", - "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", + "gui_settings_authenticate_no_auth_option": "", "gui_settings_authenticate_password_option": "Pasvorto", "gui_settings_password_label": "Pasvorto", "gui_settings_button_save": "Konservi", @@ -55,8 +55,8 @@ "settings_error_automatic": "Ne eblas konektiĝi al Tor-kontrolilo. Ĉu Tor Browser funkcias en la fono? Se vi ne havas ĝin, vi povas ekhavi ĝin je:\nhttps://www.torproject.org/.", "settings_error_socket_port": "Ne eblas konektiĝi al Tor-kontrolilo je {}:{}.", "settings_error_socket_file": "Ne eblas konektiĝi al Tor-kontrolilo per socket-dosiero {}.", - "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", - "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", + "settings_error_auth": "", + "settings_error_missing_password": "", "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work." -- cgit v1.2.3-54-g00ecf From a0ceb76d05500193f7d682ce1218eaf9955eaa20 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 16:20:59 +0000 Subject: Translated using Weblate (Georgian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ka/ --- share/locale/ka.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/ka.json b/share/locale/ka.json index a67a5f75..0f6541fa 100644 --- a/share/locale/ka.json +++ b/share/locale/ka.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "პროგრამის დატოვება", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -60,7 +60,7 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "პროგრამის დატოვება", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", @@ -127,7 +127,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "პროგრამის დატოვება", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "არჩევა", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From e433357d816a0ba564cda642f881aa40d316127c Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 14 Dec 2018 16:21:04 +0000 Subject: Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ --- share/locale/hu.json | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/share/locale/hu.json b/share/locale/hu.json index a67a5f75..2f11fd50 100644 --- a/share/locale/hu.json +++ b/share/locale/hu.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Kilépés", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Delete", + "gui_choose_items": "Kiválaszt", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Kilépés", + "gui_quit_warning_dont_quit": "Megszakítás", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Beállítások", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Soha", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Általános beállítások", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "Kontroll port", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "Mentés", + "gui_settings_button_cancel": "Megszakítás", + "gui_settings_button_help": "Súgó", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Igen", + "gui_tor_connection_ask_quit": "Kilépés", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "Megosztás", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "Bevétel", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Tallózás", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", @@ -174,12 +174,12 @@ "systray_upload_page_loaded_message": "", "gui_uploads": "", "gui_no_uploads": "", - "gui_clear_history": "", + "gui_clear_history": "Az összes törlése", "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", + "gui_settings_language_label": "Előnyben részesített nyelv", "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 30dc050f17937c451216b9a14fdfaf07578d901c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:45:14 +0000 Subject: Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ --- share/locale/id.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/share/locale/id.json b/share/locale/id.json index a67a5f75..24da50a0 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Keluar", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -32,9 +32,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "Tambahkan", + "gui_delete": "Hapus", + "gui_choose_items": "Pilih", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Keluar", + "gui_quit_warning_dont_quit": "Batal", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Pengaturan", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Tak pernah", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Pengaturan umum", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,7 +84,7 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "Port kontrol", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", @@ -101,8 +101,8 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", + "gui_settings_button_save": "Simpan", + "gui_settings_button_cancel": "Batal", "gui_settings_button_help": "", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Ya", + "gui_tor_connection_ask_quit": "Keluar", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Jelajahi", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From c6f81658a77f87842dc9f8b0d0e337efd90832ba Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:44:08 +0000 Subject: Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ --- share/locale/ko.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/share/locale/ko.json b/share/locale/ko.json index 25cd5c48..c152c33a 100644 --- a/share/locale/ko.json +++ b/share/locale/ko.json @@ -15,7 +15,7 @@ "timeout_download_still_running": "", "timeout_upload_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "종료", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -34,8 +34,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "삭제", + "gui_choose_items": "선택", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -48,7 +48,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "취소 된", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -61,22 +61,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "종료", + "gui_quit_warning_dont_quit": "취소", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "설정", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "하지 않음", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "일반 설정", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -85,13 +85,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "제어 포트", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -102,9 +102,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "저장", + "gui_settings_button_cancel": "취소", + "gui_settings_button_help": "도움말", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -128,7 +128,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "종료", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -148,7 +148,7 @@ "gui_status_indicator_share_started": "", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "수익", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -165,7 +165,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "보기", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", "systray_close_server_message": "", @@ -174,12 +174,12 @@ "systray_upload_page_loaded_message": "", "gui_uploads": "", "gui_no_uploads": "", - "gui_clear_history": "", + "gui_clear_history": "모두 삭제", "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", + "gui_settings_language_label": "선호 언어", "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 8dc0ae7b4d096bb90b451ea0a944dcdd56c85ccf Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:44:34 +0000 Subject: Translated using Weblate (Macedonian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/mk/ --- share/locale/mk.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/share/locale/mk.json b/share/locale/mk.json index a67a5f75..2273ba1e 100644 --- a/share/locale/mk.json +++ b/share/locale/mk.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Излези", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Излези", + "gui_quit_warning_dont_quit": "Откажи", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Поставки", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Никогаш", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Општи поставувања", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -101,8 +101,8 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", + "gui_settings_button_save": "Зачувување", + "gui_settings_button_cancel": "Откажи", "gui_settings_button_help": "", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", @@ -127,7 +127,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "Излези", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Преглед", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From b04e9727d6b2c800f68eeccbabbb8fe52960fda5 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:00:16 +0000 Subject: Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ --- share/locale/pl.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/share/locale/pl.json b/share/locale/pl.json index a67a5f75..c45af162 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Wyjście", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Usuń", + "gui_choose_items": "Wybierz", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Anulowano", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Wyjście", + "gui_quit_warning_dont_quit": "Anuluj", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Ustawienia", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "Sprawdź nową wersję", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Nigdy", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Ustawienia ogólne", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "Port sterowania", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Hasło", + "gui_settings_password_label": "Hasło", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "Zapisz", + "gui_settings_button_cancel": "Anuluj", + "gui_settings_button_help": "Pomoc", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Tak", + "gui_tor_connection_ask_quit": "Wyjście", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "Udostępnianie", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "Otrzymuję", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Wybierz...", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", @@ -174,12 +174,12 @@ "systray_upload_page_loaded_message": "", "gui_uploads": "", "gui_no_uploads": "", - "gui_clear_history": "", + "gui_clear_history": "Wyczyść wszystko", "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", + "gui_settings_language_label": "Preferowany język", "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 4230b770b3348ba81d9e919fcfdc0bdfd75e563f Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:42:33 +0000 Subject: Translated using Weblate (Punjabi) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pa/ --- share/locale/pa.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/pa.json b/share/locale/pa.json index a67a5f75..a2a967cc 100644 --- a/share/locale/pa.json +++ b/share/locale/pa.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "ਬਾਹਰ", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -60,7 +60,7 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "ਬਾਹਰ", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", @@ -75,7 +75,7 @@ "gui_settings_autoupdate_timestamp": "", "gui_settings_autoupdate_timestamp_never": "", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "ਜਨਰਲ ਸੈਟਿੰਗਜ਼", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -127,7 +127,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "ਬਾਹਰ", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", -- cgit v1.2.3-54-g00ecf From bb1617407724c2aefe18709e16043a76de6e0300 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:13:44 +0000 Subject: Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ --- share/locale/ro.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/share/locale/ro.json b/share/locale/ro.json index a67a5f75..b46f0b5d 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Închidere", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Şterge", + "gui_choose_items": "Alegeți", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Canceled", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Închidere", + "gui_quit_warning_dont_quit": "Anulare", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Setari", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Niciodata", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Setări generale", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "Port de control", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Parolă", + "gui_settings_password_label": "Parolă", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_save": "Salvare", + "gui_settings_button_cancel": "Anulare", + "gui_settings_button_help": "Ajutor", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Da", + "gui_tor_connection_ask_quit": "Închidere", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "Impartasirea creatiilor", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "Primire", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Răsfoiește", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From 68966c5759f8c5eb63685d39b9492db35d69cbf8 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 07:35:10 +0000 Subject: Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ --- share/locale/ru.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/share/locale/ru.json b/share/locale/ru.json index b7c89d69..54f5d64d 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,8 +1,32 @@ { "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", - "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", + "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", "not_a_file": "{0:s} не является файлом.", "gui_copied_url": "Ссылка скопирована в буфер обмена", "other_page_loaded": "Другая страница была загружена", - "gui_copy_url": "Скопировать ссылку" + "gui_copy_url": "Скопировать ссылку", + "systray_menu_exit": "Выйти", + "gui_add": "Добавить", + "gui_delete": "Удалить", + "gui_choose_items": "Выбрать", + "gui_canceled": "Отменена", + "gui_quit_warning_quit": "Выйти", + "gui_quit_warning_dont_quit": "Отмена", + "gui_settings_window_title": "Настройки", + "gui_settings_autoupdate_timestamp_never": "Никогда", + "gui_settings_general_label": "Основные настройки", + "gui_settings_control_port_label": "Контрольный порт", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_button_save": "Сохранить", + "gui_settings_button_cancel": "Отмена", + "gui_settings_button_help": "Помощь", + "gui_tor_connection_ask_open_settings": "Есть", + "gui_tor_connection_ask_quit": "Выйти", + "gui_status_indicator_share_started": "Распространение", + "gui_status_indicator_receive_started": "Доходы", + "gui_settings_downloads_label": "Сохранять файлы в", + "gui_settings_downloads_button": "Выбрать", + "gui_clear_history": "Очистить все", + "gui_settings_language_label": "Предпочтительный язык" } -- cgit v1.2.3-54-g00ecf From c913f768e5382c7f4a4dc30d20c1130ad63adf28 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 08:42:58 +0000 Subject: Translated using Weblate (Slovenian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sl/ --- share/locale/sl.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/share/locale/sl.json b/share/locale/sl.json index a67a5f75..42892ef9 100644 --- a/share/locale/sl.json +++ b/share/locale/sl.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Izhod", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -34,7 +34,7 @@ "gui_drag_and_drop": "", "gui_add": "", "gui_delete": "", - "gui_choose_items": "", + "gui_choose_items": "Izberi", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Odpovedan", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,7 +60,7 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "Izhod", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", @@ -73,9 +73,9 @@ "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Nikoli", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Splošne nastavitve", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,7 +84,7 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "Krmilna vrata", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", @@ -103,7 +103,7 @@ "gui_settings_tor_bridges_invalid": "", "gui_settings_button_save": "", "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_help": "Pomoč", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Da", + "gui_tor_connection_ask_quit": "Izhod", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Brskanje", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From 2b44e52fa91511c64f1f79c8fd44293746a57769 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 10:34:38 +0000 Subject: Add weblate versions of this files, to see if it stops complaining without a real merge. A real merge from weblate will include more files yet not translated. --- share/locale/pt_BR.json | 189 ++++++++++++++++++++++++++++++++++++++++++++++-- share/locale/pt_PT.json | 188 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 367 insertions(+), 10 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index 1b2d3139..abe7c795 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -1,7 +1,186 @@ { - "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", - "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", - "not_a_file": "{0:s} não é um arquivo.", - "gui_copied_url": "URL foi copiado para área de transferência", - "other_page_loaded": "Outra página tem sido carregada" + "config_onion_service": "Configurando o serviço onion na porta {0:d}.", + "preparing_files": "Comprimindo arquivos.", + "give_this_url": "Dar este endereço ao destinatário:", + "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", + "give_this_url_receive": "Dar este endereço ao remetente:", + "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", + "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", + "not_a_file": "{0:s} não é um ficheiro válido.", + "not_a_readable_file": "{0:s} não é um ficheiro legível.", + "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", + "other_page_loaded": "Endereço carregado", + "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", + "closing_automatically": "Interrompido porque o download terminou", + "timeout_download_still_running": "Esperando que o download termine", + "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", + "systray_menu_exit": "Sair", + "systray_download_started_title": "O download de OnionShare começou", + "systray_download_started_message": "Alguém começou fazer o download dos seus arquivos", + "systray_download_completed_title": "O download de OnionShare terminou", + "systray_download_completed_message": "Essa pessoa terminou de fazer o download dos seus arquivos", + "systray_download_canceled_title": "O download de OnionShare foi cancelado", + "systray_download_canceled_message": "Essa pessoa cancelou o download", + "systray_upload_started_title": "OnionShare começou a carregar", + "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", + "help_local_only": "Não use Tor (unicamente para programação)", + "help_stay_open": "Continuar a compartilhar depois do primeiro download", + "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", + "help_stealth": "Usar autorização de cliente (avançado)", + "help_receive": "Receber compartilhamentos ao invés de enviá-los", + "help_debug": "Registrar erros do OnionShare no stdout e erros de rede, no disco", + "help_filename": "Lista de arquivos ou pastas a compartilhar", + "help_config": "Personalizar a configuração JSON de localização de arquivos (opcional)", + "gui_drag_and_drop": "Arrastar arquivos e pastas\npara começar a compartilhá-los", + "gui_add": "Adicionar", + "gui_delete": "Apagar", + "gui_choose_items": "Escolher", + "gui_share_start_server": "Começar a compartilhar", + "gui_share_stop_server": "Parar de compartilhar", + "gui_share_stop_server_shutdown_timeout": "Parar de compartilhar ({}segundos para terminar)", + "gui_share_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às", + "gui_receive_start_server": "Modo Começar a Receber", + "gui_receive_stop_server": "Modo Parar de Receber", + "gui_receive_stop_server_shutdown_timeout": "Modo Parar de Receber ({}segundos para terminar)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "O cronômetro automático termina às {}", + "gui_copy_url": "Copiar endereço", + "gui_copy_hidservauth": "Copiar HidServAuth", + "gui_downloads": "Histórico de download", + "gui_no_downloads": "Nenhum download por enquanto", + "gui_canceled": "Cancelado", + "gui_copied_url_title": "Endereço OnionShare copiado", + "gui_copied_url": "URL foi copiado na área de transferência", + "gui_copied_hidservauth_title": "HidServAuth copiado", + "gui_copied_hidservauth": "Linha HidServAuth copiada na área de transferência", + "gui_please_wait": "Começando...Clique para cancelar.", + "gui_download_upload_progress_complete": "%p%, {0:s} decorridos.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", + "gui_download_upload_progress_eta": "{0:s}, tempo estimado para término: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Mais devagar", + "gui_share_quit_warning": "O envio dos seus arquivos ainda não terminou . Você tem certeza de que quer sair de OnionShare?", + "gui_receive_quit_warning": "O recebimento dos seus arquivos ainda não terminou. Você tem certeza de que quer sair do OnionShare?", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", + "error_rate_limit": "Alguém tentou por várias vezes acessar seu endereço, o que talvez signifique que essa pessoa esteja tentando adivinhá-lo. Por isso, OnionShare interrompeu o servidor. Recomece a compartilhar e envie um novo endereço ao seu destinatário para continuar a compartilhar.", + "zip_progress_bar_format": "Comprimindo: %p%", + "error_stealth_not_supported": "Para usar uma autorização de cliente, você precisa de ao menos de Tor 0.2.9.1-alpha (ou navegador Tor 6.5) e de python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", + "gui_settings_window_title": "Configurações", + "gui_settings_whats_this": "O que é isso?", + "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", + "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", + "gui_settings_autoupdate_label": "Procurar a nova versão", + "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", + "gui_settings_autoupdate_timestamp": "Última atualização: {}", + "gui_settings_autoupdate_timestamp_never": "Nunca", + "gui_settings_autoupdate_check_button": "Procurar a nova versão", + "gui_settings_general_label": "Configurações gerais", + "gui_settings_sharing_label": "Compartilhando configurações", + "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", + "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", + "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", + "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", + "gui_settings_connection_type_control_port_option": "Conectar usando entrada de controle", + "gui_settings_connection_type_socket_file_option": "Conectar usando um ficheiro socket", + "gui_settings_connection_type_test_button": "Testar a conexão a Tor", + "gui_settings_control_port_label": "Entrada de controle", + "gui_settings_socket_file_label": "Ficheiro socket", + "gui_settings_socks_label": "Entrada SOCKS", + "gui_settings_authenticate_label": "Configurações de autenticação do Tor", + "gui_settings_authenticate_no_auth_option": "Sem autenticação nem cookie de autenticação", + "gui_settings_authenticate_password_option": "Palavra-passe", + "gui_settings_password_label": "Palavra-passe", + "gui_settings_tor_bridges": "Ajuda para pontes Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Não usar pontes", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportadores plugáveis obfs4 já instalados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportadores plugáveis obfs4 já instalados (requer obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usar transportadores plugáveis meek_lite (Azure) instalados", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transportadores plugáveis meek_lite (Azure) instalados (requer obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Aviso: As pontes meek_lite são muito custosas para o Projeto Tor.

    Use-as somente se você não conseguir se conectar a Tor diretamente, via transportadores obfs4 ou outras pontes comuns.", + "gui_settings_tor_bridges_custom_radio_option": "Usar pontes personalizadas", + "gui_settings_tor_bridges_custom_label": "Você pode obter pontes em https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Nenhumas ponte adicionada funciona.\nTente usá-las de novo ou adicione outras.", + "gui_settings_button_save": "Salvar", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", + "gui_settings_shutdown_timeout_checkbox": "Usar cronômetro para encerrar automaticamente", + "gui_settings_shutdown_timeout": "Encerrar o compartilhamento às:", + "settings_error_unknown": "Impossível conectar-se ao controlador do Tor, porque as suas configurações estão confusas.", + "settings_error_automatic": "Não foi possível conectar ao controlador do Tor. O Navegador Tor (disponível no site torproject.org) está rodando em segundo plano?", + "settings_error_socket_port": "Não pode ligar ao controlador do Tor em {}:{}.", + "settings_error_socket_file": "Não foi possível conectar ao controlador Tor usando o arquivo de socket {}.", + "settings_error_auth": "Conectado a {}:{}, mas não foi possível autenticar. Talvez este não seja um controlador Tor?", + "settings_error_missing_password": "Conectado ao controlador Tor, mas é preciso ter uma senha para autenticar.", + "settings_error_unreadable_cookie_file": "Conectado ao controlador Tor, mas talvez a palavra-passe esteja incorreta, ou o seu usuário não possui autorização para ler o ficheiro de cookie.", + "settings_error_bundled_tor_not_supported": "Não é possível usar a versão de Tor que vem junto com OnionShare, em modo 'programação', com Windows ou macOS.", + "settings_error_bundled_tor_timeout": "A conexão a Tor está demorando muito. O seu computado está conectado à Internet e o seu relógio de sistema, ajustado?", + "settings_error_bundled_tor_broken": "OnionShare não pôde se conectar a Tor em segundo plano:\n{}", + "settings_test_success": "Conectado ao controlador Tor.\n\nVersão do Tor: {}\nPossui suporte para serviços onion efêmeros: {}.\nPossui suporte para autenticação de cliente: {}.\nPossui suporte para a próxima geração de endereços .onion: {}.", + "error_tor_protocol_error": "Houve um erro com Tor: {}", + "error_tor_protocol_error_unknown": "Ocorreu um erro desconhecido com Tor", + "error_invalid_private_key": "Este tipo de chave privada não possui suporte", + "connecting_to_tor": "Conectando à rede Tor", + "update_available": "Atualização de OnionShare disponível. Clique aqui para obtê-la.

    Você está usando a versão {} e a última é {}.", + "update_error_check_error": "Não foi possível verificar por novas versões: o website do OnionShare está dizendo que a última versão é a irreconhecível '{}'…", + "update_error_invalid_latest_version": "Não foi possível verificar por uma nova versão: talvez você não esteja conectada ao Tor, ou talvez o website do OnionShare esteja fora do ar?", + "update_not_available": "Você está rodando a última versão de OnionShare.", + "gui_tor_connection_ask": "Abrir as configurações para consertar a conexão ao Tor?", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", + "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.", + "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", + "gui_tor_connection_lost": "Desconectado do Tor.", + "gui_server_started_after_timeout": "O tempo se esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", + "gui_server_timeout_expired": "O temporizador já se esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", + "share_via_onionshare": "Compartilhar por meio de OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", + "gui_save_private_key_checkbox": "Usar um endereço persistente (modo antigo)", + "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", + "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode fazer upload de arquivos para o seu computador usando o Tor Browser: ", + "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_url_label_stay_open": "Este compartilhamento não sera encerrado automaticamente.", + "gui_url_label_onetime": "Este compartilhamento será encerrado depois da primeira vez que for utilizado.", + "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_status_indicator_share_stopped": "Pronto para compartilhar", + "gui_status_indicator_share_working": "Começando…", + "gui_status_indicator_share_started": "Compartilhando", + "gui_status_indicator_receive_stopped": "Pronto para receber", + "gui_status_indicator_receive_working": "Começando…", + "gui_status_indicator_receive_started": "Recebendo", + "gui_file_info": "{} arquivos, {}", + "gui_file_info_single": "{} ficheiro, {}", + "history_in_progress_tooltip": "{} em progresso", + "history_completed_tooltip": "{} completado", + "info_in_progress_uploads_tooltip": "{} upload(s) em progresso", + "info_completed_uploads_tooltip": "{} upload(s) completado(s)", + "error_cannot_create_downloads_dir": "Não foi possível a pasta do modo de recepção: {}", + "receive_mode_downloads_dir": "Os arquivos enviados para você aparecem na seguinte pasta: {}", + "receive_mode_warning": "Atenção: O modo de recepção permite que as pessoas enviem arquivos para o seu computador. Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", + "gui_receive_mode_warning": "O modo de recepção permite que pessoas enviem arquivos para o seu computador.

    Alguns arquivos podem tomar o controle do seu computador se você abrí-los. Apenas abra arquivos enviados por pessoas que você confia, ou se você souber o que está fazendo.", + "receive_mode_upload_starting": "Um upload de tamanho total {} está sendo iniciado", + "receive_mode_received_file": "Recebido: {}", + "gui_mode_share_button": "Compartilhar Arquivos", + "gui_mode_receive_button": "Receber Arquivos", + "gui_settings_receiving_label": "Configurações de recepção", + "gui_settings_downloads_label": "Armazenar arquivos em", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "Modo público", + "systray_close_server_title": "Servidor OnionShare encerrado", + "systray_close_server_message": "Um usuário encerrou o servidor", + "systray_page_loaded_title": "Página OnionShare Carregada", + "systray_download_page_loaded_message": "Um usuário carregou a página de download", + "systray_upload_page_loaded_message": "Um usuário carregou a página de upload", + "gui_uploads": "Histórico de Uploads", + "gui_no_uploads": "Nenhum upload realizado", + "gui_clear_history": "Limpar Tudo", + "gui_upload_in_progress": "Upload Iniciado {}", + "gui_upload_finished_range": "Upload de {} feito para {}", + "gui_upload_finished": "", + "gui_download_in_progress": "Download Iniciado {}", + "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", + "gui_settings_language_label": "Idioma preferido", + "gui_settings_language_changed_notice": "Reinicie o OnionShare para que sua alteração de idioma tenha efeito.", + "timeout_upload_still_running": "Esperando o término do upload" } diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index 1b2d3139..a67a5f75 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -1,7 +1,185 @@ { - "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", - "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", - "not_a_file": "{0:s} não é um arquivo.", - "gui_copied_url": "URL foi copiado para área de transferência", - "other_page_loaded": "Outra página tem sido carregada" + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" } -- cgit v1.2.3-54-g00ecf From 34a912623d7908f312622886f7a03db6f4d28e72 Mon Sep 17 00:00:00 2001 From: drebs Date: Sat, 15 Dec 2018 10:48:22 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index abe7c795..f0c839ef 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -164,7 +164,7 @@ "gui_mode_receive_button": "Receber Arquivos", "gui_settings_receiving_label": "Configurações de recepção", "gui_settings_downloads_label": "Armazenar arquivos em", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Navegar", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare encerrado", @@ -177,7 +177,7 @@ "gui_clear_history": "Limpar Tudo", "gui_upload_in_progress": "Upload Iniciado {}", "gui_upload_finished_range": "Upload de {} feito para {}", - "gui_upload_finished": "", + "gui_upload_finished": "Upload realizado de {}", "gui_download_in_progress": "Download Iniciado {}", "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", "gui_settings_language_label": "Idioma preferido", -- cgit v1.2.3-54-g00ecf From 0868ae52c19b77989cb384337c230b540d5aefd7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 11:02:28 +0000 Subject: Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ --- share/locale/pt_PT.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index a67a5f75..2fa056ee 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -1,15 +1,15 @@ { "config_onion_service": "", "preparing_files": "", - "give_this_url": "", + "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", + "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "not_a_file": "{0:s} não é um arquivo.", "not_a_readable_file": "", "no_available_port": "", - "other_page_loaded": "", + "other_page_loaded": "Outra página tem sido carregada", "close_on_timeout": "", "closing_automatically": "", "timeout_download_still_running": "", @@ -49,7 +49,7 @@ "gui_no_downloads": "", "gui_canceled": "", "gui_copied_url_title": "", - "gui_copied_url": "", + "gui_copied_url": "URL foi copiado para área de transferência", "gui_copied_hidservauth_title": "", "gui_copied_hidservauth": "", "gui_please_wait": "", -- cgit v1.2.3-54-g00ecf From 78b876d748dde14b03995cfc56de1083f2634d09 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 12:23:21 +0100 Subject: Deleted translation using Weblate (Tachelhit) --- share/locale/shi.json | 185 -------------------------------------------------- 1 file changed, 185 deletions(-) delete mode 100644 share/locale/shi.json diff --git a/share/locale/shi.json b/share/locale/shi.json deleted file mode 100644 index a67a5f75..00000000 --- a/share/locale/shi.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" -} -- cgit v1.2.3-54-g00ecf From 668a6db142b89af40a5088286173faad66b4288c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 11:22:14 +0000 Subject: Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ --- share/locale/pt_PT.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index 2fa056ee..7085f2c2 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Sair", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Eliminar", + "gui_choose_items": "Escolha", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Cancelada", "gui_copied_url_title": "", "gui_copied_url": "URL foi copiado para área de transferência", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Definições", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Definições gerais", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -89,8 +89,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -102,8 +102,8 @@ "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Navegar", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From dc529e2da67b770523cd1b28bc7972023e0b547c Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 11:53:18 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index 7e075aaf..a6f6d265 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -82,5 +82,6 @@ "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", "gui_settings_language_label": "Lingua preferita", - "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto." + "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", + "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati" } -- cgit v1.2.3-54-g00ecf From d31d13bc1bf2b9c2ff97e59138b960f9dd33cc76 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 11:45:38 +0000 Subject: Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ --- share/locale/zh_Hant.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index a67a5f75..c24d13ce 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "離開", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -32,9 +32,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "新增", + "gui_delete": "刪除", + "gui_choose_items": "選擇", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "取消", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "離開", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "設定", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "檢查新版本", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "不使用", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "一般設定", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "管理連接埠", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", + "gui_settings_button_save": "保存", "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_help": "協助", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -127,7 +127,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "離開", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "分享", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "收取", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "瀏覽", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From 371417838ce3837ee94a33cf2d84a76a4157b71a Mon Sep 17 00:00:00 2001 From: Weblate push user Date: Sat, 15 Dec 2018 13:01:53 +0100 Subject: Update from Weblate (#4) * Translated using Weblate --- share/locale/it.json | 3 +- share/locale/pt_PT.json | 42 +++++------ share/locale/shi.json | 185 ---------------------------------------------- share/locale/zh_Hant.json | 38 +++++----- 4 files changed, 42 insertions(+), 226 deletions(-) delete mode 100644 share/locale/shi.json diff --git a/share/locale/it.json b/share/locale/it.json index 7e075aaf..a6f6d265 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -82,5 +82,6 @@ "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", "gui_settings_language_label": "Lingua preferita", - "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto." + "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", + "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati" } diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index a67a5f75..7085f2c2 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -1,20 +1,20 @@ { "config_onion_service": "", "preparing_files": "", - "give_this_url": "", + "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", + "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "not_a_file": "{0:s} não é um arquivo.", "not_a_readable_file": "", "no_available_port": "", - "other_page_loaded": "", + "other_page_loaded": "Outra página tem sido carregada", "close_on_timeout": "", "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Sair", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Eliminar", + "gui_choose_items": "Escolha", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,9 +47,9 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Cancelada", "gui_copied_url_title": "", - "gui_copied_url": "", + "gui_copied_url": "URL foi copiado para área de transferência", "gui_copied_hidservauth_title": "", "gui_copied_hidservauth": "", "gui_please_wait": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Definições", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Definições gerais", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -89,8 +89,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -102,8 +102,8 @@ "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Navegar", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", diff --git a/share/locale/shi.json b/share/locale/shi.json deleted file mode 100644 index a67a5f75..00000000 --- a/share/locale/shi.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" -} diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json index a67a5f75..c24d13ce 100644 --- a/share/locale/zh_Hant.json +++ b/share/locale/zh_Hant.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "離開", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -32,9 +32,9 @@ "help_filename": "", "help_config": "", "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_add": "新增", + "gui_delete": "刪除", + "gui_choose_items": "選擇", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "取消", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "離開", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "設定", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_label": "檢查新版本", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "不使用", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "一般設定", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -84,13 +84,13 @@ "gui_settings_connection_type_control_port_option": "", "gui_settings_connection_type_socket_file_option": "", "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", + "gui_settings_control_port_label": "管理連接埠", "gui_settings_socket_file_label": "", "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -101,9 +101,9 @@ "gui_settings_tor_bridges_custom_radio_option": "", "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", + "gui_settings_button_save": "保存", "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_help": "協助", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -127,7 +127,7 @@ "update_not_available": "", "gui_tor_connection_ask": "", "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_quit": "離開", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -144,10 +144,10 @@ "gui_url_label_onetime_and_persistent": "", "gui_status_indicator_share_stopped": "", "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", + "gui_status_indicator_share_started": "分享", "gui_status_indicator_receive_stopped": "", "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", + "gui_status_indicator_receive_started": "收取", "gui_file_info": "", "gui_file_info_single": "", "history_in_progress_tooltip": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "瀏覽", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From 1f3728e8c55389122f1d692d129980ec03b43780 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 16 Dec 2018 10:55:13 -0800 Subject: If a locale file includes a blank string, fallback to English instead of using the blank string --- onionshare/strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/strings.py b/onionshare/strings.py index b730933d..643186dd 100644 --- a/onionshare/strings.py +++ b/onionshare/strings.py @@ -45,7 +45,7 @@ def load_strings(common): current_locale = common.settings.get('locale') strings = {} for s in translations[default_locale]: - if s in translations[current_locale]: + if s in translations[current_locale] and translations[current_locale][s] != "": strings[s] = translations[current_locale][s] else: strings[s] = translations[default_locale][s] -- cgit v1.2.3-54-g00ecf From a06bb0878f5a7c0ebf20ceff48f278b9ae5e5e1a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 16 Dec 2018 17:20:28 -0800 Subject: When enabling debug mode in Web, use common.build_data_dir() to get data dir path --- onionshare/web/web.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 21e9cd8f..0f156941 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -184,19 +184,7 @@ class Web(object): """ Turn on debugging mode, which will log flask errors to a debug file. """ - if self.common.platform == 'Windows': - try: - appdata = os.environ['APPDATA'] - flask_debug_filename = '{}\\OnionShare\\flask_debug.log'.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) - flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log') - elif self.common.platform == 'Darwin': - flask_debug_filename = os.path.expanduser('~/Library/Application Support/OnionShare/flask_debug.log') - else: - flask_debug_filename = os.path.expanduser('~/.config/onionshare/flask_debug.log') - + flask_debug_filename = os.path.join(self.common.build_data_dir(), 'flask_debug.log') log_handler = logging.FileHandler(flask_debug_filename) log_handler.setLevel(logging.WARNING) self.app.logger.addHandler(log_handler) -- cgit v1.2.3-54-g00ecf From 0dae2baa3eca2affd10865f4acfd9c6fdc4b8df7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 17 Dec 2018 09:33:47 +0000 Subject: Added translation using Weblate (Amharic) --- share/locale/am.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/am.json diff --git a/share/locale/am.json b/share/locale/am.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/am.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From af2af016118ea013a62a5ca342f6fea51dca8783 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 17 Dec 2018 09:34:03 +0000 Subject: Added translation using Weblate (Luganda) --- share/locale/lg.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/lg.json diff --git a/share/locale/lg.json b/share/locale/lg.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/lg.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From acf5690c2204ab038201c2773bc3eb37259b1aa1 Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 17 Dec 2018 09:34:21 +0000 Subject: Added translation using Weblate (Yoruba) --- share/locale/yo.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/yo.json diff --git a/share/locale/yo.json b/share/locale/yo.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/yo.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 4d3d6ef2239b43900df5b7cdef30efd7aaf41cfd Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 15 Dec 2018 12:35:59 +0000 Subject: Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ --- share/locale/is.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/share/locale/is.json b/share/locale/is.json index a67a5f75..85b492ab 100644 --- a/share/locale/is.json +++ b/share/locale/is.json @@ -14,7 +14,7 @@ "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Hætta", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -60,7 +60,7 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", + "gui_quit_warning_quit": "Hætta", "gui_quit_warning_dont_quit": "", "error_rate_limit": "", "zip_progress_bar_format": "", @@ -73,9 +73,9 @@ "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Aldrei", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Almennar stillingar", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Já", + "gui_tor_connection_ask_quit": "Hætta", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Flakka", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From ce2efec61091d714127b9d07a003beb819090545 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Dec 2018 18:13:02 -0800 Subject: Delete install/Info.plist, because PyInstaller doesn't actually even use it. And bump Mac Tor Browser version to 8.0.4, but I'll still need to bump it again before the 2.0 release --- install/Info.plist | 18 ------------------ install/get-tor-osx.py | 6 +++--- 2 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 install/Info.plist diff --git a/install/Info.plist b/install/Info.plist deleted file mode 100644 index 0125c1d5..00000000 --- a/install/Info.plist +++ /dev/null @@ -1,18 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - onionshare-gui - CFBundleIdentifier - com.micahflee.onionshare - NSHighResolutionCapable - - CFBundleShortVersionString - {VERSION} - CFBundleIconFile - icon.icns - - diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index ae20fd74..61c2a4bf 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -35,9 +35,9 @@ import subprocess import requests def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/TorBrowser-8.0.1-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.0.1-osx64_en-US.dmg' - expected_dmg_sha256 = 'fb1be2a0f850a65bae38747c3abbf9061742c5d7799e1693405078aaf38d2b08' + dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.4/TorBrowser-8.0.4-osx64_en-US.dmg' + dmg_filename = 'TorBrowser-8.0.4-osx64_en-US.dmg' + expected_dmg_sha256 = '44433ee2052cf3062e0dc29e640a6ae50db2775bc8939253f5f9d81614f2db07' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) -- cgit v1.2.3-54-g00ecf From e6f26f65459489de6c59dda8bc8e4abc213ba762 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Dec 2018 18:53:40 -0800 Subject: When discovering default locale, default to english if locale.getdefaultlocale() returns None. Also, make locales that include country codes (pt_PT and pt_BR) actually work as default locales --- onionshare/settings.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index fc68ffc9..91844c8d 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -111,7 +111,19 @@ class Settings(object): # 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] + language_code, encoding = locale.getdefaultlocale() + + # Default to English + if not language_code: + language_code = 'en_US' + + if language_code == 'pt_PT' and language_code == 'pt_BR': + # Portuguese locales include country code + default_locale = language_code + else: + # All other locales cut off the country code + default_locale = language_code[:2] + if default_locale not in self.available_locales: default_locale = 'en' self._settings['locale'] = default_locale -- cgit v1.2.3-54-g00ecf From 498fe64617c0b22b1cfbfcccc6f7cbfedfd92bc8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 18 Dec 2018 19:39:40 -0800 Subject: Version bump to 2.0.dev1, and updated changelog --- CHANGELOG.md | 9 +++++++++ share/version.txt | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 136dd2b1..9686439e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # OnionShare Changelog +## 2.0 + +* New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files +* New feature: If you're sharing a single file, don't zip it up +* New feature: macOS sandbox enabled +* New feature: Allow selecting your language from a dropdown +* New translations: TODO fill in for final release +* Several bugfixes + ## 1.3.1 * Updated Tor to 0.2.3.10 diff --git a/share/version.txt b/share/version.txt index 22351bb8..aa8add45 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.0.dev +2.0.dev1 -- cgit v1.2.3-54-g00ecf From ec5eff5f9de3762aa14e3ca2df72800c10856c2d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Dec 2018 11:42:37 -0800 Subject: Tweak the changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9686439e..e437b939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ ## 2.0 * New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files +* New feature: macOS sandbox is enabled +* New feature: Support for next generation onion services (TODO waiting on Tor release) * New feature: If you're sharing a single file, don't zip it up -* New feature: macOS sandbox enabled * New feature: Allow selecting your language from a dropdown -* New translations: TODO fill in for final release +* New translations: (TODO fill in for final release) * Several bugfixes ## 1.3.1 -- cgit v1.2.3-54-g00ecf From 6e0081cebaf9d02aafc772b3ea11a156c718214b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Dec 2018 11:45:31 -0800 Subject: pwd module doesn't exist in Windows --- onionshare/settings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index a1d96d26..38478dbd 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -22,7 +22,12 @@ import json import os import platform import locale -import pwd + +try: + # We only need pwd module in macOS, and it's not available in Windows + import pwd +except: + pass from . import strings -- cgit v1.2.3-54-g00ecf From 391619e6b55345a07421b784c486af53c9726884 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 19 Dec 2018 11:47:41 -0800 Subject: Update version and size for Windows release --- install/onionshare.nsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index f0b28535..3a4c6c2a 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -3,10 +3,10 @@ !define ABOUTURL "https:\\onionshare.org\" # change these with each release -!define INSTALLSIZE 66537 -!define VERSIONMAJOR 1 -!define VERSIONMINOR 3 -!define VERSIONSTRING "1.3.1" +!define INSTALLSIZE 115186 +!define VERSIONMAJOR 2 +!define VERSIONMINOR 0 +!define VERSIONSTRING "2.0" RequestExecutionLevel admin -- cgit v1.2.3-54-g00ecf From 494ab53b07d1ede3730cf9f6288e94515fe4f537 Mon Sep 17 00:00:00 2001 From: Kevin Gallagher Date: Wed, 19 Dec 2018 14:47:42 -0800 Subject: Add some informational trove classifiers for PyPi To make finding the tool easier. --- setup.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 86b71f82..4169297b 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,16 @@ author_email = 'micah@micahflee.com' url = 'https://github.com/micahflee/onionshare' license = 'GPL v3' keywords = 'onion, share, onionshare, tor, anonymous, web server' +classifiers = [ + "Programming Language :: Python :: 3", + "Framework :: Flask", + "Topic :: Communications :: File Sharing", + "Topic :: Security :: Cryptography", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Intended Audience :: End Users/Desktop", + "Operating System :: OS Independent", + "Environment :: Web Environment" + ] data_files=[ (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']), (os.path.join(sys.prefix, 'share/metainfo'), ['install/onionshare.appdata.xml']), @@ -63,8 +73,8 @@ if platform.system() != 'OpenBSD': setup( name='onionshare', version=version, description=description, long_description=long_description, - author=author, author_email=author_email, - url=url, license=license, keywords=keywords, + author=author, author_email=author_email, maintainer=author, maintainer_email=author_email, + url=url, license=license, keywords=keywords, classifiers=classifiers, packages=[ 'onionshare', 'onionshare.web', -- cgit v1.2.3-54-g00ecf From 64cc9427e5948d2b88783c8e53c94ed9505107e9 Mon Sep 17 00:00:00 2001 From: Kevin Gallagher Date: Wed, 19 Dec 2018 16:04:31 -0800 Subject: Make sure the OnionShare data directory is created Fixes #850 --- onionshare/common.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index c84046f0..d0fe79db 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -130,15 +130,18 @@ class Common(object): if self.platform == 'Windows': try: appdata = os.environ['APPDATA'] - return '{}\\OnionShare'.format(appdata) + onionshare_data_dir = '{}\\OnionShare'.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_data_dir = '~/.config/onionshare' elif self.platform == 'Darwin': - return os.path.expanduser('~/Library/Application Support/OnionShare') + onionshare_data_dir = '~/Library/Application Support/OnionShare' else: - return os.path.expanduser('~/.config/onionshare') + onionshare_data_dir = '~/.config/onionshare' + + os.makedirs(onionshare_data_dir, 0o700, True) + return onionshare_data_dir def build_slug(self): """ -- cgit v1.2.3-54-g00ecf From e501bb4de8e9e40055378561601e3526bdafa883 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 20 Dec 2018 14:02:52 -0800 Subject: Make settings dialog checkboxes line up in macOS --- onionshare/common.py | 7 +++++++ onionshare_gui/settings_dialog.py | 1 + 2 files changed, 8 insertions(+) diff --git a/onionshare/common.py b/onionshare/common.py index c84046f0..0b7d3631 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -395,6 +395,13 @@ class Common(object): 'settings_connect_to_tor': """ QLabel { font-style: italic; + }""", + + # For some reason, this prevents extra padding around the v2 onion + # settings when viewing in macOS + 'settings_onion_settings': """ + QWidget { + border: 0; }""" } diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 92c84262..60bc0946 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -162,6 +162,7 @@ class SettingsDialog(QtWidgets.QDialog): onion_settings_layout.addWidget(hidservauth_details) onion_settings_layout.addWidget(self.hidservauth_copy_button) self.onion_settings_widget = QtWidgets.QWidget() + self.onion_settings_widget.setStyleSheet(self.common.css['settings_onion_settings']) self.onion_settings_widget.setLayout(onion_settings_layout) # General options layout -- cgit v1.2.3-54-g00ecf From 12fc9fd568b6682d33cda9bc79f77217a0172b9b Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 17 Dec 2018 10:02:36 +0000 Subject: Translated using Weblate (Amharic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/am/ --- share/locale/am.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/am.json b/share/locale/am.json index 25cd5c48..d226d86e 100644 --- a/share/locale/am.json +++ b/share/locale/am.json @@ -62,7 +62,7 @@ "gui_share_quit_warning": "", "gui_receive_quit_warning": "", "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_dont_quit": "ተወው", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", @@ -103,8 +103,8 @@ "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_cancel": "ተወው", + "gui_settings_button_help": "መመሪያ", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -127,7 +127,7 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_open_settings": "አዎ", "gui_tor_connection_ask_quit": "", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", -- cgit v1.2.3-54-g00ecf From c8b77ab3d80327dde285b414dae4ff04263dad6f Mon Sep 17 00:00:00 2001 From: emma peel Date: Mon, 17 Dec 2018 19:29:05 +0000 Subject: Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ --- share/locale/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/hu.json b/share/locale/hu.json index 2f11fd50..34cc480b 100644 --- a/share/locale/hu.json +++ b/share/locale/hu.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From 4dade70236ec78059194019eb0f534de48593b44 Mon Sep 17 00:00:00 2001 From: Atef Ben Ali Date: Fri, 21 Dec 2018 09:12:40 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index a2313ac1..4abe35e1 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,7 +1,7 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", + "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", + "preparing_files": "ضغط الملفات", + "give_this_url": "أعط هذا العنوان للمستقبل", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", -- cgit v1.2.3-54-g00ecf From 5d7449c47618e2103ef65a775386a181fcaa9e6e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 10:46:09 -0800 Subject: Use os.path.expanduser when creating data dir, and no need to try creating data dir in Settings.save because it's created in Config.build_data_dir --- onionshare/common.py | 6 +++--- onionshare/settings.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index d740d2ed..49c69ab5 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -134,11 +134,11 @@ class Common(object): except: # If for some reason we don't have the 'APPDATA' environment variable # (like running tests in Linux while pretending to be in Windows) - onionshare_data_dir = '~/.config/onionshare' + onionshare_data_dir = os.path.expanduser('~/.config/onionshare') elif self.platform == 'Darwin': - onionshare_data_dir = '~/Library/Application Support/OnionShare' + onionshare_data_dir = os.path.expanduser('~/Library/Application Support/OnionShare') else: - onionshare_data_dir = '~/.config/onionshare' + onionshare_data_dir = os.path.expanduser('~/.config/onionshare') os.makedirs(onionshare_data_dir, 0o700, True) return onionshare_data_dir diff --git a/onionshare/settings.py b/onionshare/settings.py index 38478dbd..06235198 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -185,7 +185,6 @@ class Settings(object): Save settings to file. """ self.common.log('Settings', 'save') - os.makedirs(os.path.dirname(self.filename), exist_ok=True) open(self.filename, 'w').write(json.dumps(self._settings)) self.common.log('Settings', 'save', 'Settings saved in {}'.format(self.filename)) -- cgit v1.2.3-54-g00ecf From c8be7a16094523435dc46f730563f8062989c731 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 12:21:47 -0800 Subject: Add python3-distutils dependency --- BUILD.md | 2 +- stdeb.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index 7663ec23..1e135251 100644 --- a/BUILD.md +++ b/BUILD.md @@ -14,7 +14,7 @@ Install the needed dependencies: For Debian-like distros: ``` -apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python +apt install -y python3-flask python3-stem python3-pyqt5 python3-crypto python3-socks python3-distutils python-nautilus tor obfs4proxy python3-pytest build-essential fakeroot python3-all python3-stdeb dh-python ``` For Fedora-like distros: diff --git a/stdeb.cfg b/stdeb.cfg index 6729d1b7..f32730fa 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python-nautilus, tor, obfs4proxy +Depends3: python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy +Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy Suite: bionic X-Python3-Version: >= 3.5.3 -- cgit v1.2.3-54-g00ecf From 2b05952f7e604be3202ac731a2cf9b866b8c8def Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:18:34 -0800 Subject: New screenshots, including new appdata screenshots --- install/onionshare.appdata.xml | 18 +++++++++++++----- screenshots/appdata-client.png | Bin 57679 -> 0 bytes screenshots/appdata-onionshare-receive-client.png | Bin 0 -> 392602 bytes screenshots/appdata-onionshare-receive-server.png | Bin 0 -> 350674 bytes screenshots/appdata-onionshare-share-client.png | Bin 0 -> 407909 bytes screenshots/appdata-onionshare-share-server.png | Bin 0 -> 528447 bytes screenshots/appdata-server.png | Bin 119402 -> 0 bytes screenshots/onionshare-receive-client.png | Bin 59463 -> 63044 bytes screenshots/onionshare-receive-server.png | Bin 68038 -> 73910 bytes 9 files changed, 13 insertions(+), 5 deletions(-) delete mode 100644 screenshots/appdata-client.png create mode 100644 screenshots/appdata-onionshare-receive-client.png create mode 100644 screenshots/appdata-onionshare-receive-server.png create mode 100644 screenshots/appdata-onionshare-share-client.png create mode 100644 screenshots/appdata-onionshare-share-server.png delete mode 100644 screenshots/appdata-server.png diff --git a/install/onionshare.appdata.xml b/install/onionshare.appdata.xml index fd78616e..f145a0ee 100644 --- a/install/onionshare.appdata.xml +++ b/install/onionshare.appdata.xml @@ -1,5 +1,5 @@ - + onionshare.desktop CC0-1.0 @@ -21,12 +21,20 @@ - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-server.png -
    + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-server.png + - https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-client.png - + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-share-client.png + + + + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-server.png + + + + https://raw.githubusercontent.com/micahflee/onionshare/master/screenshots/appdata-onionshare-receive-client.png + https://onionshare.org/ diff --git a/screenshots/appdata-client.png b/screenshots/appdata-client.png deleted file mode 100644 index 8fe99b11..00000000 Binary files a/screenshots/appdata-client.png and /dev/null differ diff --git a/screenshots/appdata-onionshare-receive-client.png b/screenshots/appdata-onionshare-receive-client.png new file mode 100644 index 00000000..49f13adb Binary files /dev/null and b/screenshots/appdata-onionshare-receive-client.png differ diff --git a/screenshots/appdata-onionshare-receive-server.png b/screenshots/appdata-onionshare-receive-server.png new file mode 100644 index 00000000..af2f3659 Binary files /dev/null and b/screenshots/appdata-onionshare-receive-server.png differ diff --git a/screenshots/appdata-onionshare-share-client.png b/screenshots/appdata-onionshare-share-client.png new file mode 100644 index 00000000..08cb864c Binary files /dev/null and b/screenshots/appdata-onionshare-share-client.png differ diff --git a/screenshots/appdata-onionshare-share-server.png b/screenshots/appdata-onionshare-share-server.png new file mode 100644 index 00000000..83cc1951 Binary files /dev/null and b/screenshots/appdata-onionshare-share-server.png differ diff --git a/screenshots/appdata-server.png b/screenshots/appdata-server.png deleted file mode 100644 index abdb2198..00000000 Binary files a/screenshots/appdata-server.png and /dev/null differ diff --git a/screenshots/onionshare-receive-client.png b/screenshots/onionshare-receive-client.png index 7702a4bd..82beb23f 100644 Binary files a/screenshots/onionshare-receive-client.png and b/screenshots/onionshare-receive-client.png differ diff --git a/screenshots/onionshare-receive-server.png b/screenshots/onionshare-receive-server.png index 0962a74f..798e49eb 100644 Binary files a/screenshots/onionshare-receive-server.png and b/screenshots/onionshare-receive-server.png differ -- cgit v1.2.3-54-g00ecf From e95c420f0c7ab48d6012347aa44a7a553cf3a46d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:28:20 -0800 Subject: Update description of OnionShare --- README.md | 4 +++- install/onionshare.appdata.xml | 17 ++++++++++------- setup.py | 23 +++++++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index caa4cb62..68b93574 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # OnionShare -[OnionShare](https://onionshare.org) lets you securely and anonymously share files of any size. It works by starting a web server, making it accessible as a Tor Onion Service, and generating an unguessable URL to access and download the files. It does _not_ require setting up a separate server or using a third party file-sharing service. You host the files on your own computer and use a Tor Onion Service to make it temporarily accessible over the internet. The receiving user just needs to open the URL in Tor Browser to download the file. +[OnionShare](https://onionshare.org) lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service. + +If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor onion service to make them temporarily accessible over the internet. The receiving user just needs to open the web address in Tor Browser to download the files. If you want to receive files, OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to make it temporarily accessible over the internet. Other users can upload files to you from by loading the web address in Tor Browser. ## Documentation diff --git a/install/onionshare.appdata.xml b/install/onionshare.appdata.xml index f145a0ee..2302a2e8 100644 --- a/install/onionshare.appdata.xml +++ b/install/onionshare.appdata.xml @@ -8,15 +8,18 @@ Securely and anonymously share a file of any size

    - OnionShare lets you securely and anonymously share a file of any size with someone. It works - by starting a web server, making it accessible as a Tor hidden service, and generating an - unguessable URL to access and download the file. + OnionShare lets you securely and anonymously send and receive files. It works by starting a + web server, making it accessible as a Tor onion service, and generating an unguessable web + address so others can download files from you, or upload files to you. It does not + require setting up a separate server or using a third party file-sharing service.

    - It doesn't require setting up a server on the internet somewhere or using a third party - filesharing service. You host the file on your own computer and use a Tor hidden service to - make it temporarily accessible over the internet. The other user just needs to use Tor Browser - to download the file from you. + If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor + onion service to make them temporarily accessible over the internet. The receiving user just + needs to open the web address in Tor Browser to download the files. If you want to receive files, + OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to + make it temporarily accessible over the internet. Other users can upload files to you from by + loading the web address in Tor Browser.

    diff --git a/setup.py b/setup.py index 4169297b..f482abb6 100644 --- a/setup.py +++ b/setup.py @@ -31,14 +31,21 @@ def file_list(path): version = open('share/version.txt').read().strip() description = ( - """OnionShare lets you securely and anonymously share a file of any size with someone. """ - """It works by starting a web server, making it accessible as a Tor hidden service, """ - """and generating an unguessable URL to access and download the file.""") -long_description = description + " " + ( - """It doesn't require setting up a server on the internet somewhere or using a third """ - """party filesharing service. You host the file on your own computer and use a Tor """ - """hidden service to make it temporarily accessible over the internet. The other user """ - """just needs to use Tor Browser to download the file from you.""" + """OnionShare lets you securely and anonymously send and receive files. It """ + """works by starting a web server, making it accessible as a Tor onion """ + """service, and generating an unguessable web address so others can download """ + """files from you, or upload files to you. It does _not_ require setting up """ + """a separate server or using a third party file-sharing service.""" +) +long_description = description + "\n\n" + ( + """If you want to send files to someone, OnionShare hosts them on your own """ + """computer and uses a Tor onion service to make them temporarily accessible """ + """over the internet. The receiving user just needs to open the web address """ + """in Tor Browser to download the files. If you want to receive files, """ + """OnionShare hosts an anonymous dropbox directly on your computer and uses """ + """a Tor onion service to make it temporarily accessible over the internet. """ + """Other users can upload files to you from by loading the web address in """ + """Tor Browser.""" ) author = 'Micah Lee' author_email = 'micah@micahflee.com' -- cgit v1.2.3-54-g00ecf From 39bd209eddf66e1b0d2e573718465a17e9a84ab4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:29:56 -0800 Subject: Add 1.3.2 to changelog, and remake the 2.0 changelog to 2.0.dev2 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e437b939..4082a6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # OnionShare Changelog -## 2.0 +## 2.0.dev2 * New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files * New feature: macOS sandbox is enabled @@ -10,6 +10,10 @@ * New translations: (TODO fill in for final release) * Several bugfixes +## 1.3.2 + +* Bug fix: In debug mode, stop saving flask debug log in /tmp, where all users can access it + ## 1.3.1 * Updated Tor to 0.2.3.10 -- cgit v1.2.3-54-g00ecf From 2af311a65be7d1a907df174635880039d0a5ef98 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:30:17 -0800 Subject: Version bump to 2.0.dev2 --- share/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/version.txt b/share/version.txt index aa8add45..8be5e08a 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.0.dev1 +2.0.dev2 -- cgit v1.2.3-54-g00ecf From 219114dfc66012989fda3691413f9f77f0aef1ad Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:32:17 -0800 Subject: Add full meek_lite support to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4082a6e8..26ea4646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * New feature: macOS sandbox is enabled * New feature: Support for next generation onion services (TODO waiting on Tor release) * New feature: If you're sharing a single file, don't zip it up +* New feature: Full support for meek_lite (Azure) bridges * New feature: Allow selecting your language from a dropdown * New translations: (TODO fill in for final release) * Several bugfixes -- cgit v1.2.3-54-g00ecf From ecb23fc8c6976975ac08544ae7ecf030d8e28c46 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 21 Dec 2018 19:34:13 -0800 Subject: Add refactoring and unit tests to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ea4646..6f0b7aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * New feature: Allow selecting your language from a dropdown * New translations: (TODO fill in for final release) * Several bugfixes +* Invisible to users, but this version includes some major refactoring of the codebase, and a robust set of unit tests which makes OnionShare easier to maintain going forward ## 1.3.2 -- cgit v1.2.3-54-g00ecf From 0c1d1b7538982b4f7dc7055a2604dd7fb743c8ba Mon Sep 17 00:00:00 2001 From: Mattias Bertolino Date: Sat, 22 Dec 2018 20:11:43 +0000 Subject: Added translation using Weblate (Swedish) --- share/locale/sv.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/sv.json diff --git a/share/locale/sv.json b/share/locale/sv.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/sv.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 480d4153f610086c1fdbb9410be1179a7dce963f Mon Sep 17 00:00:00 2001 From: apapac Date: Sat, 22 Dec 2018 16:31:42 +0000 Subject: Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ --- share/locale/el.json | 168 +++++++++++++++++++++++++-------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/share/locale/el.json b/share/locale/el.json index 77f0b85b..dcdc97f1 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -1,10 +1,10 @@ { - "config_onion_service": "", + "config_onion_service": "Δημιουργία onion service στην πύλη {0:d}.", "preparing_files": "Συμπίεση αρχείων.", "give_this_url": "Δώσε αυτή την διεύθυνση στον/στην παραλήπτη/τρια:", - "give_this_url_stealth": "", + "give_this_url_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και τη σειρά HidServAuth ως παραλήπτη:", "give_this_url_receive": "Δώσε αυτή τη διεύθυνση στον/στην αποστολέα:", - "give_this_url_receive_stealth": "", + "give_this_url_receive_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και το HidServAuth ως αποστολέα:", "ctrlc_to_stop": "Πάτα Ctrl+C για να σταματήσεις το σέρβερ", "not_a_file": "{0:s} δεν είναι έγκυρο αρχείο.", "not_a_readable_file": "Το {0:s} δεν είναι αναγνώσιμο αρχείο.", @@ -26,89 +26,89 @@ "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά τη πρώτη λήψη", "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", - "help_stealth": "", + "help_stealth": "Χρησιμοποιήστε άδεια χρήστη (Για προχωρημένους)", "help_receive": "", "help_debug": "", - "help_filename": "", + "help_filename": "Λίστα αρχείων ή φακέλων για μοίρασμα", "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", + "gui_drag_and_drop": "Σύρτε και αφήστε αρχεία και φακέλους\nγια να αρχίσετε να τα μοιράζεστε", + "gui_add": "Προσθήκη", + "gui_delete": "Διαγραφή", + "gui_choose_items": "Επιλογή", + "gui_share_start_server": "Εκκίνηση μοιράσματος", + "gui_share_stop_server": "Τερματισμός μοιράσματος", + "gui_share_stop_server_shutdown_timeout": "Τερματισμός μοιράσματος (απομένουν {}\")", + "gui_share_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", + "gui_receive_start_server": "Εκκίνηση κατάστασης λήψης", + "gui_receive_stop_server": "Τερματισμός κατάστασης λήψης", + "gui_receive_stop_server_shutdown_timeout": "Τερματισμός κατάστασης λήψης (υπολοίπονται {}\")", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", + "gui_copy_url": "Αντιγραφή διεύθυνσης", + "gui_copy_hidservauth": "Αντιγραφή HidServAuth", + "gui_downloads": "Ιστορικό Λήψεων", + "gui_no_downloads": "Καμία λήψη ως τώρα", + "gui_canceled": "Ακυρώθηκε", + "gui_copied_url_title": "Αντεγραμμένη διεύθυνση OnionShare", + "gui_copied_url": "Αντεγράφη η διεύθυνση OnionShare στον πίνακα", + "gui_copied_hidservauth_title": "Αντιγραμμένος HidServAuth", + "gui_copied_hidservauth": "Η σειρά HidServAuth αντεγράφη στον πίνακα", + "gui_please_wait": "Ξεκινάμε... Κάντε κλικ για ακύρωση.", + "gui_download_upload_progress_complete": "%p%, {0:s} πέρασαν.", + "gui_download_upload_progress_starting": "{0:s}, %p% (υπολογισμός)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Όχι τόσο γρήγορα", + "gui_share_quit_warning": "Είστε στη διαδικασία αποστολής αρχείων. Είστε σίγουρος πως θέλετε να ακυρώσετε το OnionShare?", + "gui_receive_quit_warning": "Είστε στη διαδικασία παραλαβής αρχείων. Είστε σίγουρος πώς θέλετε να ακυρώσετε το OnionShare?", + "gui_quit_warning_quit": "Έξοδος", + "gui_quit_warning_dont_quit": "Ακύρωση", + "error_rate_limit": "Κάποιος προσπάθησε επανειλημμένα να μπει στη διεύθυνσή σας, το οποίο σημαίνει πως μπορεί να προσπαθεί να την μαντέψει, οπότε το OnionShare σταμάτησε τον server. Ξεκινήστε πάλι το μοίρασμα και στείλτε στον παραλήπτη μία νέα διεύθυνση για κοινοποίηση.", + "zip_progress_bar_format": "Συμπίεση: %p%", + "error_stealth_not_supported": "Για τη χρήση άδειας χρήστη, χρειάζεστε τουλάχιστον το Tor 0.2.9.1-alpha (ή τον Tor Browser 6.5) και το python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", + "gui_settings_window_title": "Ρυθμίσεις", + "gui_settings_whats_this": " Τί είναι αυτό? ", + "gui_settings_stealth_option": "Χρήση άδειας χρήστη (αδειοδότηση)", + "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα \nνα επιλέξετε την αντιγραφή του HidServAuth σας.", + "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", + "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", + "gui_settings_autoupdate_timestamp": "Τελευταίος έλεγχος: {}", + "gui_settings_autoupdate_timestamp_never": "Ποτέ", + "gui_settings_autoupdate_check_button": "Έλεγχος για νέα έκδοση", + "gui_settings_general_label": "Γενικές ρυθμίσεις", + "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης", + "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης μετά την πρώτη λήψη", + "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?", + "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare", + "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser", + "gui_settings_connection_type_control_port_option": "Σύνδεση μέσω πύλης ελέγχου", + "gui_settings_connection_type_socket_file_option": "Σύνδεση μέσω αρχείου μετάβασης", + "gui_settings_connection_type_test_button": "Έλεγχος της σύνδεσης με το Tor", + "gui_settings_control_port_label": "Πύλη ελέγχου", + "gui_settings_socket_file_label": "Αρχείο μετάβασης", + "gui_settings_socks_label": "πύλη SOCKS", + "gui_settings_authenticate_label": "Ρυθμίσεις επαλήθευσης Tor", + "gui_settings_authenticate_no_auth_option": "Καμία επαλήθευση ή επαλήθευση cookie", + "gui_settings_authenticate_password_option": "Κωδικός", + "gui_settings_password_label": "Κωδικός", + "gui_settings_tor_bridges": "Στήριξη Tor bridge", + "gui_settings_tor_bridges_no_bridges_radio_option": "Μην χρησιμοποιείτε bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Χρησιμοποιήστε ενσωματωμένα obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Χρησμοποιήστε ενσωματωμένα obfs4 pluggable transports (απαιτείται obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports (απαιτεί obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Προσοχή: Τα meek_lite bridges επιβαρύνουν πολύ το Tor Project στη λειτουργία.

    Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλα κανονικά bridges.", + "gui_settings_tor_bridges_custom_radio_option": "Χρήση κανονικών bridges", + "gui_settings_tor_bridges_custom_label": "Αποκτήστε bridges στο https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Δεν λειτούργησε κάποιο από τα bridges που προσθέσατε.\nΞαναελέγξτε τα ή επιλέξτε άλλα.", + "gui_settings_button_save": "Αποθήκευση", + "gui_settings_button_cancel": "Ακύρωση", + "gui_settings_button_help": "Βοήθεια", + "gui_settings_shutdown_timeout_checkbox": "Χρήση χρονομέτρου αυτόματου τερματισμού", + "gui_settings_shutdown_timeout": "Τερματισμός της κοινοποίησης στα:", + "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.", + "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?", + "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.", "settings_error_socket_file": "", "settings_error_auth": "", "settings_error_missing_password": "", @@ -182,5 +182,5 @@ "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", "gui_settings_language_changed_notice": "", - "timeout_upload_still_running": "Αναμονή ολοκλήρωση του ανεβάσματος" + "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος" } -- cgit v1.2.3-54-g00ecf From 424d500af7cf7a76da4ef83e77575f6bfa60bc16 Mon Sep 17 00:00:00 2001 From: jinofe zdfpost Date: Sun, 23 Dec 2018 08:00:47 +0000 Subject: Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ --- share/locale/ru.json | 177 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 12 deletions(-) diff --git a/share/locale/ru.json b/share/locale/ru.json index 54f5d64d..254b5ef8 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,9 +1,9 @@ { - "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", - "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", - "not_a_file": "{0:s} не является файлом.", - "gui_copied_url": "Ссылка скопирована в буфер обмена", - "other_page_loaded": "Другая страница была загружена", + "give_this_url": "Передайте этот адрес получателю:", + "ctrlc_to_stop": "Нажмите Ctrl+C, чтобы остановить сервер", + "not_a_file": "{0:s} недопустимый файл.", + "gui_copied_url": "Ссылка OnionShare скопирована в буфер обмена", + "other_page_loaded": "Адрес загружен", "gui_copy_url": "Скопировать ссылку", "systray_menu_exit": "Выйти", "gui_add": "Добавить", @@ -14,19 +14,172 @@ "gui_quit_warning_dont_quit": "Отмена", "gui_settings_window_title": "Настройки", "gui_settings_autoupdate_timestamp_never": "Никогда", - "gui_settings_general_label": "Основные настройки", + "gui_settings_general_label": "Общие настройки", "gui_settings_control_port_label": "Контрольный порт", - "gui_settings_authenticate_password_option": "Password", - "gui_settings_password_label": "Password", + "gui_settings_authenticate_password_option": "Пароль", + "gui_settings_password_label": "Пароль", "gui_settings_button_save": "Сохранить", "gui_settings_button_cancel": "Отмена", "gui_settings_button_help": "Помощь", "gui_tor_connection_ask_open_settings": "Есть", "gui_tor_connection_ask_quit": "Выйти", - "gui_status_indicator_share_started": "Распространение", - "gui_status_indicator_receive_started": "Доходы", + "gui_status_indicator_share_started": "Идёт раздача", + "gui_status_indicator_receive_started": "Идёт получение", "gui_settings_downloads_label": "Сохранять файлы в", "gui_settings_downloads_button": "Выбрать", - "gui_clear_history": "Очистить все", - "gui_settings_language_label": "Предпочтительный язык" + "gui_clear_history": "Очистить Все", + "gui_settings_language_label": "Предпочтительный язык", + "config_onion_service": "Назначем \"луковому\" сервису порт {:d}.", + "preparing_files": "Сжимаем файлы.", + "give_this_url_stealth": "Передайте этот адрес и строку HidServAuth получателю:", + "give_this_url_receive": "Передайте этот адрес отправителю:", + "give_this_url_receive_stealth": "Передайте этот адрес и строку HidServAuth отправителю:", + "not_a_readable_file": "{0:s} не читаемый файл.", + "no_available_port": "Не удалось найти доступный порт для запуска \"лукового\" сервиса", + "close_on_timeout": "Время ожидания таймера истекло, сервис остановлен", + "closing_automatically": "Загрузка завершена, сервис остановлен", + "timeout_download_still_running": "Ожидаем завершения загрузки", + "timeout_upload_still_running": "Ожидаем завершения выгрузки", + "large_filesize": "Внимание: Отправка раздачи большого объёма может занять продолжительное время (часы)", + "systray_download_started_title": "OnionShare: Загрузка Началась", + "systray_download_started_message": "Пользователь начал загружать ваши файлы", + "systray_download_completed_title": "OnionShare: Загрузка Завершена", + "systray_download_completed_message": "Пользователь завершил загрузку ваших файлов", + "systray_download_canceled_title": "OnionShare: Загрузка Отменена", + "systray_download_canceled_message": "Пользователь отменил загрузку", + "systray_upload_started_title": "OnionShare: Выгрузка Началась", + "systray_upload_started_message": "Пользователь начал выгрузку файлов на ваш компьютер", + "help_local_only": "Не использовать Tor (только для разработки)", + "help_stay_open": "Продолжить раздачу после первой загрузки", + "help_shutdown_timeout": "Остановить раздачу после заданного количества секунд", + "help_stealth": "Использовать авторизацию клиента (дополнительно)", + "help_receive": "Получать раздачи, вместо их отправки", + "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск", + "help_filename": "Список файлов или папок для раздачи", + "help_config": "Расположение пользовательского конфигурационного JSON-файла (необязательно)", + "gui_drag_and_drop": "Перетащите сюда файлы и папки\nчтобы начать раздачу", + "gui_share_start_server": "Начать раздачу", + "gui_share_stop_server": "Закончить раздачу", + "gui_share_stop_server_shutdown_timeout": "Остановить раздачу ({}s осталось)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", + "gui_receive_start_server": "Включить Режим Получения", + "gui_receive_stop_server": "Выключить Режим Получения", + "gui_receive_stop_server_shutdown_timeout": "Выключить Режим Получения ({}s осталось)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", + "gui_copy_hidservauth": "Скопировать строку HidServAuth", + "gui_downloads": "История Загрузок", + "gui_no_downloads": "Пока нет загрузок", + "gui_copied_url_title": "Адрес OnionShare скопирован", + "gui_copied_hidservauth_title": "Строка HidServAuth скопирована", + "gui_copied_hidservauth": "Строка HidServAuth скопирована в буфер обмена", + "gui_please_wait": "Начинаем... нажмите, чтобы отменить.", + "gui_download_upload_progress_complete": "%p%, прошло {0:s}.", + "gui_download_upload_progress_starting": "{0:s}, %p% (вычисляем)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Не так быстро", + "gui_share_quit_warning": "Идёт процесс отправки файлов. Вы уверены, что хотите завершить работу OnionShare?", + "gui_receive_quit_warning": "Идёт процесс получения файлов. Вы уверены, что хотите завершить работу OnionShare?", + "error_rate_limit": "Кто-то совершил слишком много неверных попыток подключения к вашему адресу, это может означать, что его пытаются вычислить. OnionShare остановил сервер. Создайте раздачу повторно и перешлите получателю новый адрес.", + "zip_progress_bar_format": "Сжатие: %p%", + "error_stealth_not_supported": "Для использования авторизации клиента необходимы как минимум версии Tor 0.2.9.1-alpha (или Tor Browser 6.5) и библиотеки python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Для работы OnionShare необходимы как минимум версии Tor 0.2.7.1 и библиотеки python3-stem 1.4.0.", + "gui_settings_whats_this": "Что это?", + "gui_settings_stealth_option": "Использовать авторизацию клиента (устарело)", + "gui_settings_stealth_hidservauth_string": "Сохранили ваш приватный ключ для повторного использования,\nзначит теперь вы можете нажать сюда, чтобы скопировать вашу строку HidServAuth.", + "gui_settings_autoupdate_label": "Проверить новую версию", + "gui_settings_autoupdate_option": "Уведомить меня, когда будет доступна новая версия", + "gui_settings_autoupdate_timestamp": "Последняя проверка: {}", + "gui_settings_autoupdate_check_button": "Проверить Новую Версию", + "gui_settings_sharing_label": "Настройки раздачи", + "gui_settings_close_after_first_download_option": "Остановить раздачу после первой загрузки", + "gui_settings_connection_type_label": "Как OnionShare следует подключиться к сети Tor?", + "gui_settings_connection_type_bundled_option": "Использовать версию Tor, встроенную в OnionShare", + "gui_settings_connection_type_automatic_option": "Попробовать автоматическую настройку с Tor Browser", + "gui_settings_connection_type_control_port_option": "Подключиться используя порт управления", + "gui_settings_connection_type_socket_file_option": "Подключиться используя файл-сокет", + "gui_settings_connection_type_test_button": "Проверить подключение к сети Tor", + "gui_settings_socket_file_label": "Файл-сокет", + "gui_settings_socks_label": "Порт SOCKS", + "gui_settings_authenticate_label": "Настройки аутентификации Tor", + "gui_settings_authenticate_no_auth_option": "Без аутентификации или cookie-аутентификации", + "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Не использовать \"мосты\"", + "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные obfs4 подключаемые транспорты", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные obfs4 подключаемые транспорты (необходим obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные meek_lite (Azure) встроенные транспорты", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные meek_lite (Azure) встроенные транспорты (необходим obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project

    Используйте их только если Вы не можете поделючться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".", + "gui_settings_tor_bridges_custom_radio_option": "Использовать пользовательские \"мосты\"", + "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работет.\nПроверьте их снова или добавьте другие.", + "gui_settings_shutdown_timeout_checkbox": "Использовать таймер", + "gui_settings_shutdown_timeout": "Остановить раздачу в:", + "settings_error_unknown": "Невозможно подключить к контроллеру Tor, поскольку ваши настройки не корректны.", + "settings_error_automatic": "Невозможно подключться к контроллеру Tor. Уточните, Tor Browser (можно загрузить по ссылке torproject.org) запущен в фоновом режиме?", + "settings_error_socket_port": "Невозможно подключиться к контроллеру Tor в {}:{}.", + "settings_error_socket_file": "Невозможно подключиться к контроллеру Tor используя файл-сокет {}.", + "settings_error_auth": "Произведено подлючение к {}:{}, не получается проверить подлинность. Возможно, это не контроллер сети Tor?", + "settings_error_missing_password": "Произведено подключение к контроллеру Tor, но для аутентификации необходим пароль.", + "settings_error_unreadable_cookie_file": "Произведено подключение к контроллеру Tor, но пароль может быть указан неверно или пользователю не разрешено чтение файла-cookie.", + "settings_error_bundled_tor_not_supported": "Версию Tor, которая поставляется вместе с OnionShare нельзя использовать в режиме разработки на операционных системах Windows или macOS.", + "settings_error_bundled_tor_timeout": "Подключение к сети Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет или у вас неточно настроено системное время?", + "settings_error_bundled_tor_broken": "OnionShare не смог подулючиться к сети Tor в фоновом режиме:\n{}", + "settings_test_success": "Произведено подключение к контроллеру Tor.\n\nВерсия Tor: {}\nПоддержка временных \"луковых\" сервисов: {}.\nПоддержка аутентификации клиента: {}.\nПоддержка адресов .onion следующего поколения: {}.", + "error_tor_protocol_error": "Произошла ошибка с сетью Tor: {}", + "error_tor_protocol_error_unknown": "Произошла неизвестная ошибка с сетю Tor", + "error_invalid_private_key": "Данный тип приватного ключа не поддерживается", + "connecting_to_tor": "Подключаемся к сети Tor", + "update_available": "Вышла новая версия OnionShare. Нажмите сюда чтобы загрузить.

    Вы используется версию {}, наиболее поздняя версия {}.", + "update_error_check_error": "Не удалось проверить новые версии: сайт OnionShare сообщает, что не удалось распознать наиболее позднюю версию '{}'…", + "update_error_invalid_latest_version": "Не удалось проверить наличие новой версии: возможно вы не подключены к сети Tor или сайт OnionShare не работает?", + "update_not_available": "Вы используете наиболее позднюю версию OnionShare.", + "gui_tor_connection_ask": "Открыть раздел \"настройки\" для решения проблем с подключением к сети Tor?", + "gui_tor_connection_error_settings": "Попробуйте изменить способ, при помощий которого OnionShare подключается к сети Tor в разделе \"Настройки\".", + "gui_tor_connection_canceled": "Не удалось подключиться к сети Tor.\n\nПожалуйста, убедитесь что есть подключение к сети Интернет, затем переоткройте OnionShare и настройте подключение к сети Tor.", + "gui_tor_connection_lost": "Произведено отключение от сети Tor.", + "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, создайте новую раздачу.", + "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для того, чтобы начать раздачу.", + "share_via_onionshare": "OnionShare это", + "gui_use_legacy_v2_onions_checkbox": "Используйте устаревшие адреса", + "gui_save_private_key_checkbox": "Используйте постоянный адрес (устарело)", + "gui_share_url_description": "Кто угодно c этим адресом OnionShare может загрузить ваши файлы при помощиTor Browser: ", + "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может выгрузить файлы на ваш комьютер Tor Browser: ", + "gui_url_label_persistent": "Данная раздаче не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_url_label_stay_open": "Данная раздача не будет остановлена автоматически.", + "gui_url_label_onetime": "Данная раздача будет завершена автоматически после первой загрузки.", + "gui_url_label_onetime_and_persistent": "Данная раздача не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_status_indicator_share_stopped": "Можно начинать раздачу", + "gui_status_indicator_share_working": "Начинаем…", + "gui_status_indicator_receive_stopped": "Можно начинать получение", + "gui_status_indicator_receive_working": "Начинаем…", + "gui_file_info": "{} файлы, {}", + "gui_file_info_single": "{} файл, {}", + "history_in_progress_tooltip": "{} в ходе выполнения", + "history_completed_tooltip": "{} завершено", + "info_in_progress_uploads_tooltip": "{} выгрузка(и) в ходе выполнения", + "info_completed_uploads_tooltip": "{} выгрузка(и) завершена(ы)", + "error_cannot_create_downloads_dir": "Не удалось создать папку в режиме получения: {}", + "receive_mode_downloads_dir": "Файлы, которые были вам отправлены находятся в папке: {}", + "receive_mode_warning": "Внимание: Режим получения позаоляет другия людями загружать файлы на ваш компьютер. Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", + "gui_receive_mode_warning": "Режим получения позаоляет другия людями загружать файлы на ваш компьютер.

    Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", + "receive_mode_upload_starting": "Начинается выгрузка общим объёмом {}", + "receive_mode_received_file": "Получено: {}", + "gui_mode_share_button": "Раздача файлов", + "gui_mode_receive_button": "Получение файлов", + "gui_settings_receiving_label": "Настройки получения", + "gui_settings_public_mode_checkbox": "Публичный режим", + "systray_close_server_title": "Сервер OnionShare Отключен", + "systray_close_server_message": "Пользователь отключил сервер", + "systray_page_loaded_title": "Страница OnionShare Загружена", + "systray_download_page_loaded_message": "Пользователь находится на странице загрузки", + "systray_upload_page_loaded_message": "Пользователь посетил странцу выгрузки", + "gui_uploads": "История Выгрузок", + "gui_no_uploads": "Пока Нет Выгрузок", + "gui_upload_in_progress": "Выгрузка Началась {}", + "gui_upload_finished_range": "Загружено {} в {}", + "gui_upload_finished": "Выгружено {}", + "gui_download_in_progress": "Загрузка Началась {}", + "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку nautilus не доступен. Файл находится здесь: {}", + "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки." } -- cgit v1.2.3-54-g00ecf From ea579c73727bd3cc3c4f2c84389b9f9f585a945a Mon Sep 17 00:00:00 2001 From: Adrià García-Alzórriz Date: Sun, 23 Dec 2018 13:18:49 +0000 Subject: Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ --- share/locale/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/ca.json b/share/locale/ca.json index fbe11606..068ed541 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -121,7 +121,7 @@ "error_tor_protocol_error_unknown": "Hi ha hagut un error desconegut amb Tor", "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", "connecting_to_tor": "Connectant a la xarxa Tor", - "update_available": "", + "update_available": "Ha sortit una nova versió d'OnionShare.Feu clic aquí per obtenir-la.

    Esteu usant {} i la més recent és {}.", "update_error_check_error": "", "update_error_invalid_latest_version": "", "update_not_available": "", -- cgit v1.2.3-54-g00ecf From fe0e873010c6f3ce43e831c7bd5ff04f54695eef Mon Sep 17 00:00:00 2001 From: Harm ten Napel Date: Sun, 23 Dec 2018 10:41:35 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 156 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 117 insertions(+), 39 deletions(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index f27bd1ea..3ecc763b 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -23,50 +23,50 @@ "help_stay_open": "Blijven delen na afronden van eerste download", "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", - "help_debug": "Log fouten naar harde schijf", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", - "help_config": "Pad naar een JSON configuratie bestand (optioneel)", + "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", "gui_add": "Toevoegen", "gui_delete": "Verwijder", "gui_choose_items": "Kies", "gui_copy_url": "Kopieer URL", "gui_copy_hidservauth": "Kopieer HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Download Geschiedenis", "gui_canceled": "Afgebroken", - "gui_copied_url": "URL gekopieerd naar klembord", + "gui_copied_url": "OnionShare adres gekopieerd naar klembord", "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", - "gui_please_wait": "Moment geduld...", - "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", + "gui_please_wait": "Aan het starten... Klik om te annuleren.", + "gui_download_upload_progress_complete": "%p%, {0:s} verstreken.", + "gui_download_upload_progress_starting": "{0:s}, %p% (berekenen)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Niet afsluiten", - "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.", - "zip_progress_bar_format": "Bestanden verwerken: %p%", - "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.", - "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.", + "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", + "zip_progress_bar_format": "Comprimeren: %p%", + "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.", + "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", - "gui_settings_stealth_option": "Maak stealth onion services", - "gui_settings_autoupdate_label": "Controleer voor updates", - "gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn", + "gui_settings_stealth_option": "Gebruik client authorization (achterhaald)", + "gui_settings_autoupdate_label": "Controleer op updates", + "gui_settings_autoupdate_option": "Laat me weten als er een nieuwe versie beschikbaar is", "gui_settings_autoupdate_timestamp": "Laatste controle: {}", "gui_settings_autoupdate_timestamp_never": "Nooit", - "gui_settings_autoupdate_check_button": "Controleer voor update", - "gui_settings_sharing_label": "Deel opties", + "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie", + "gui_settings_sharing_label": "Instelling voor delen", "gui_settings_close_after_first_download_option": "Stop delen na eerste download", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", - "gui_settings_connection_type_bundled_option": "Gebruik Tor die is meegeleverd met OnionShare", - "gui_settings_connection_type_automatic_option": "Probeer automatische configuratie met Tor Browser", + "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare", + "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser", "gui_settings_connection_type_control_port_option": "Verbinden via controle poort", "gui_settings_connection_type_socket_file_option": "Verbinden via socket bestand", - "gui_settings_connection_type_test_button": "Test Tor instellingen", + "gui_settings_connection_type_test_button": "Test Connectie naar Tor", "gui_settings_control_port_label": "Controle poort", "gui_settings_socket_file_label": "Socket bestand", "gui_settings_socks_label": "SOCKS poort", - "gui_settings_authenticate_label": "Tor authenticatie opties", + "gui_settings_authenticate_label": "Tor authenticatie instellingen", "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", "gui_settings_password_label": "Wachtwoord", @@ -75,34 +75,112 @@ "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout": "Stop delen om:", "settings_saved": "Instellingen opgeslagen in {}", - "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.", - "settings_error_automatic": "Kan geen verbinding maken met de Tor controller. Draait Tor Browser in de achtergrond? Deze kan je verkrijgen via https://www.torproject.org/.", + "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat je instellingen nergens op slaan.", + "settings_error_automatic": "Kon geen verbinding maken met de Tor controller. Draait Tor Browser (beschikbaar via torproject.org) in de achtergrond?", "settings_error_socket_port": "Kan geen verbinding maken met de Tor controller op {}:{}.", "settings_error_socket_file": "Kan geen verbinding maken met de Tor controller via socket bestand {}.", "settings_error_auth": "Verbonden met {}:{}, maar kan niet authenticeren. Misschien is het geen Tor controller?", "settings_error_missing_password": "Verbonden met Tor controller, maar het heeft een wachtwoord nodig voor authenticatie.", - "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.", - "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.", - "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.", + "settings_error_unreadable_cookie_file": "Verbonden met de Tor controller, maar het wachtwoord kan onjuist zijn, of je gebruiker heeft geen toestemming om het cookie bestand te lezen.", + "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de developer mode op Windows of macOS.", + "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", - "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}", - "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.", + "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", + "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", - "update_available": "Er is een OnionShare update beschikbaar. Klik hier om te downloaden.

    Geinstalleerde versie: {}
    Laatste versie: {}", - "update_error_check_error": "Fout bij controleren voor updates: Misschien ben je niet verbonden met Tor, of misschien is de OnionShare website down.", - "update_error_invalid_latest_version": "Fout bij controleren voor updates: De OnionShare website zegt dat de laatste versie '{}' is, maar dat lijkt geen valide versie te zijn.", - "update_not_available": "Je draait de laatste versie van OnionShare.", - "gui_tor_connection_ask": "Wil je de OnionShare instellingen openen om het verbindingsprobleem met Tor op te lossen?", - "gui_tor_connection_ask_open_settings": "Open Instellingen", + "update_available": "Er is een nieuwe versie van OnionShare beschikbaar. Klik hier om te updaten.

    Je hebt nu {} en de laatste versie is {}.", + "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", + "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of is de OnionShare website is niet beschikbaar?", + "update_not_available": "Je draait de laatst beschikbare OnionShare.", + "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", + "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afsluiten", - "gui_tor_connection_error_settings": "Probeer de instellingen hoe OnionShare verbind met het Tor network aan te passen in Instellingen.", - "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.", - "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.", - "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.", + "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.", + "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.", + "gui_server_started_after_timeout": "De auto-stop timer liep af voordat de server startte.\nMaak een nieuwe share aan.", + "gui_server_timeout_expired": "De auto-stop timer is al verlopen.\nStel een nieuwe tijd in om te beginnen met delen.", "share_via_onionshare": "Deel via OnionShare", "give_this_url_receive": "Geef dit adres aan de afzender:", "give_this_url_receive_stealth": "Geef dit adres en de HidServAuth-regel aan de afzender:", "systray_upload_started_title": "OnionShare-upload gestart", "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer", - "help_receive": "Bestanden ontvangen in plaats van ze versturen" + "help_receive": "Bestanden ontvangen in plaats van ze versturen", + "timeout_upload_still_running": "Wachten op voltooiing van de upload", + "gui_share_start_server": "Start delen", + "gui_share_stop_server": "Stop delen", + "gui_share_stop_server_shutdown_timeout": "Stop Delen ({}s resterend)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer eindigt bij {}", + "gui_receive_start_server": "Start Ontvangst Mode", + "gui_receive_stop_server": "Stop Ontvangst Mode", + "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangst Mode ({}s resterend)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer stopt bij {}", + "gui_no_downloads": "Nog Geen Downloads", + "gui_copied_url_title": "Gekopieerd OnionShare Adres", + "gui_copied_hidservauth_title": "HidServAuth gekopieerd", + "gui_quit_title": "Niet zo snel", + "gui_receive_quit_warning": "Je ben bezig files te ontvangen. Weet je zeker dat je OnionShare wilt stoppen?", + "gui_settings_whats_this": "1What is dit?2", + "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je klikken om je HidServAuth te kopiëren.", + "gui_settings_general_label": "Algemene instellingen", + "gui_settings_tor_bridges": "Tor bridge ondersteuning", + "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Gebruik ingebouwde obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Gebruik ingebouwde pluggable transports (vereist obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Gebruik ingebouwde meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Gebruik ingebouwde meek_lite (Azure) pluggable transports (vereist obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Waarschuwing: De meek_lite bridges zijn erg kostbaar voor het Tor Project om uit te voeren.

    Gebruik ze alleen als je niet direct met Tor kan verbinden, via obfs4 transports, of andere normale bridges.", + "gui_settings_tor_bridges_custom_radio_option": "Gebruik custom bridges", + "gui_settings_tor_bridges_custom_label": "Je kan bridges krijgen via 1https://bridges.torproject.org2", + "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. Controleer ze of voeg andere toe.", + "gui_settings_shutdown_timeout_checkbox": "Gebruik auto-stop timer", + "error_tor_protocol_error_unknown": "Er was een onbekende fout met Tor", + "error_invalid_private_key": "Dit type privésleutel wordt niet ondersteund", + "gui_tor_connection_lost": "De verbinding met Tor is verbroken.", + "gui_use_legacy_v2_onions_checkbox": "Gebruik ouderwetse adressen", + "gui_save_private_key_checkbox": "Gebruik een blijvend adres (achterhaald)", + "gui_share_url_description": "1Iedereen2 met dit OnionShare adres kan je bestanden 3binnenhalen4 met de 5Tor Browser6: ", + "gui_receive_url_description": "1Iedereen2 met dit OnionShare adres kan bestanden op je computer 3plaatsen4 met de 5Tor Browser6: 7", + "gui_url_label_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", + "gui_url_label_stay_open": "Deze share stopt niet automatisch.", + "gui_url_label_onetime": "Deze share stopt na de eerste voltooiing.", + "gui_url_label_onetime_and_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", + "gui_status_indicator_share_stopped": "Klaar om te delen", + "gui_status_indicator_share_working": "Starten…", + "gui_status_indicator_share_started": "Aan het delen", + "gui_status_indicator_receive_stopped": "Klaar om te ontvangen", + "gui_status_indicator_receive_working": "Starten…", + "gui_status_indicator_receive_started": "Ontvangen", + "gui_file_info": "{} bestanden, {}", + "gui_file_info_single": "{} bestand, {}", + "history_in_progress_tooltip": "{} bezig", + "history_completed_tooltip": "{} klaar", + "info_in_progress_uploads_tooltip": "{} upload(s) zijn bezig", + "info_completed_uploads_tooltip": "de {} upload(s) zijn klaar", + "error_cannot_create_downloads_dir": "Kon de ontvangst mode map niet maken: {}", + "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}", + "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", + "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten.

    Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", + "receive_mode_upload_starting": "Upload met totale grootte {} is aan het starten", + "receive_mode_received_file": "Ontvangen: {}", + "gui_mode_share_button": "Deel Bestanden", + "gui_mode_receive_button": "Ontvang Bestanden", + "gui_settings_receiving_label": "Instellingen voor Ontvangen", + "gui_settings_downloads_label": "Sla bestanden op naar", + "gui_settings_downloads_button": "Surf", + "gui_settings_public_mode_checkbox": "Publieke mode", + "systray_close_server_title": "OnionShare Server Afgesloten", + "systray_close_server_message": "Een gebruiker heeft de server gestopt", + "systray_page_loaded_title": "OnionShare Pagina Geladen", + "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen", + "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen", + "gui_uploads": "Upload geschiedenis", + "gui_no_uploads": "Er zijn nog geen uploads", + "gui_clear_history": "Wis alles", + "gui_upload_in_progress": "Upload is gestart{}", + "gui_upload_finished_range": "{} is naar {} gestuurd", + "gui_upload_finished": "Verzonden {}", + "gui_download_in_progress": "Binnenhalen gestart {}", + "gui_open_folder_error_nautilus": "Kan de map niet openen omdat bestandsbeheerprogramma nautilus niet beschikbaar is. Het bestand staat hier : {}", + "gui_settings_language_label": "Voorkeurstaal", + "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd." } -- cgit v1.2.3-54-g00ecf From e72d12c7048508b0c6101909f1c18c0d99f04ac3 Mon Sep 17 00:00:00 2001 From: Mattias Bertolino Date: Sat, 22 Dec 2018 20:12:25 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 98 ++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index 25cd5c48..38ac4fa7 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -1,56 +1,56 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "config_onion_service": "Förbereder onion-tjänsten på port {0:d}.", + "preparing_files": "Komprimera filer.", + "give_this_url": "Ge den här adressen till mottagaren:", + "give_this_url_stealth": "Ge den här adressen och HidServAuth-raden till mottagaren:", + "give_this_url_receive": "Ge den här adressen till avsändaren:", + "give_this_url_receive_stealth": "Ge den här adressen och HidServAuth-raden till avsändaren:", + "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern", + "not_a_file": "{0:s} är inte en giltig fil.", + "not_a_readable_file": "{0:s} är inte en läsbar fil.", + "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", + "other_page_loaded": "Adress laddad", "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", + "closing_automatically": "Stannade för att nedladdningen blev klar", + "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", + "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", + "large_filesize": "Varning: Att skicka en stor fil kan ta timmar", + "systray_menu_exit": "Avsluta", + "systray_download_started_title": "OnionShare Nedladdning Startad", + "systray_download_started_message": "En användare började ladda ner dina filer", + "systray_download_completed_title": "OnionShare Nedladdning Klar", + "systray_download_completed_message": "Användaren har laddat ner dina filer", + "systray_download_canceled_title": "OnionShare Nedladdning Avbruten", + "systray_download_canceled_message": "Användaren avbröt nedladdningen", + "systray_upload_started_title": "OnionShare Uppladdning Påbörjad", + "systray_upload_started_message": "En användare började ladda upp filer på din dator", + "help_local_only": "Använd inte Tor (endast för utveckling)", + "help_stay_open": "Fortsätt dela efter första nedladdning", "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", + "help_stealth": "Använd klient-auktorisering (avancerat)", + "help_receive": "Ta emot delningar istället för att skicka dem", + "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", + "help_filename": "Lista filer och mappar att dela", + "help_config": "Egenvald sökväg för JSON konfigurationsfil (valfri)", + "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att påbörja delning", + "gui_add": "Lägg till", + "gui_delete": "Radera", + "gui_choose_items": "Välj", + "gui_share_start_server": "Påbörja delning", + "gui_share_stop_server": "Avbryt delning", + "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", + "gui_receive_start_server": "Starta Mottagarläge", + "gui_receive_stop_server": "Avsluta Mottagarläge", + "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer avslutas kl {}", + "gui_copy_url": "Kopiera Adress", + "gui_copy_hidservauth": "Kopiera HidServAuth", + "gui_downloads": "Nedladdningshistorik", + "gui_no_downloads": "Inga Nedladdningar Än", + "gui_canceled": "Avbruten", + "gui_copied_url_title": "OnionShare Adress Kopierad", + "gui_copied_url": "OnionShare adress kopierad till urklipp", "gui_copied_hidservauth_title": "", "gui_copied_hidservauth": "", "gui_please_wait": "", -- cgit v1.2.3-54-g00ecf From dae819e4d02f579c3f0fa200ed36b93952187a1e Mon Sep 17 00:00:00 2001 From: MA Date: Tue, 25 Dec 2018 04:23:10 +0000 Subject: Added translation using Weblate (Bulgarian) --- share/locale/bg.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 share/locale/bg.json diff --git a/share/locale/bg.json b/share/locale/bg.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/bg.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From a6cd589385efb9b4d2f4607f4ca86328d437e7e6 Mon Sep 17 00:00:00 2001 From: Stefan Leibfarth Date: Mon, 24 Dec 2018 08:03:39 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 30f5eed0..3aae1951 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -92,7 +92,7 @@ "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", - "zip_progress_bar_format": "Packe: %p%", + "zip_progress_bar_format": "Komprimiere: %p%", "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", @@ -101,29 +101,29 @@ "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", "gui_settings_general_label": "Allgemeine Einstellungen", - "gui_settings_sharing_label": "Servereinstellungen", - "gui_settings_connection_type_automatic_option": "Versuche mit dem Tor Browser automatisch zu konfigurieren", + "gui_settings_sharing_label": "Freigabe-Einstellungen", + "gui_settings_connection_type_automatic_option": "Versuche auto-konfiguartion mittels des Tor Browsers", "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", - "gui_settings_tor_bridges": "Einstellungen für Tor bridges", + "gui_settings_tor_bridges": "Unterstützung für Tor Bridges", "gui_settings_meek_lite_expensive_warning": "Achtung: Die meek_lite bridges sind für das Tor Projekt sehr kostspielig.

    Nutze sie nur, wenn du dich nicht direkt, per obfs4 Transport oder über andere, normale bridges zum Tornetzwerk verbinden kannst.", "gui_settings_tor_bridges_invalid": "Keine der bridges, die du angegeben hast, funktionieren.\nÜberprüfe sie oder gebe Andere an.", "settings_error_unknown": "Kann nicht zum Tor controller verbinden, weil deine Einstellungen keinen Sinn ergeben.", "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", - "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer hat keine Rechte, die Cookiedatei zu lesen.", - "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare gekoppelt ist nicht nutzen.", + "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer darf die Cookiedatei nicht lesen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", - "settings_error_bundled_tor_broken": "OnionShare konnte nicht zu Tor im Hintergrund verbinden:\n{}", - "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion Adressen der nächsten Generation: {}.", + "settings_error_bundled_tor_broken": "OnionShare konnte in Hintergrund nicht mit Tor verbinden:\n{}", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Klient-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", - "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShareSeite sagt, die letzte Version ist die unkenntliche '{}'…", + "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShare-Seite sagt, die aktuelle Version ist nicht wiederzuerkennen '{}'…", "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?", "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.", - "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk zu ermöglichen?", + "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk in Ordnung zu bringen?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.", "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", @@ -136,13 +136,13 @@ "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.", "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.", "gui_status_indicator_share_working": "Starte…", - "gui_status_indicator_share_started": "Läuft", + "gui_status_indicator_share_started": "Teilen", "gui_status_indicator_receive_stopped": "Bereit zum Empfangen", "gui_status_indicator_receive_working": "Starte…", "gui_status_indicator_receive_started": "Empfange", "gui_file_info": "{} Dateien, {}", "gui_file_info_single": "{} Datei, {}", - "history_completed_tooltip": "{} vollständige", + "history_completed_tooltip": "{} abgeschlossen", "info_in_progress_uploads_tooltip": "{} Upload(s) laufen", "info_completed_uploads_tooltip": "{} Upload(s) vollständig", "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", @@ -152,11 +152,11 @@ "receive_mode_received_file": "Empfangen: {}", "gui_mode_share_button": "Versende Dateien", "gui_mode_receive_button": "Empfange Dateien", - "gui_settings_receiving_label": "Empfangseinstellungen", + "gui_settings_receiving_label": "Empfangs-Einstellungen", "gui_settings_downloads_label": "Speichere Dateien in", "gui_settings_downloads_button": "Durchsuchen", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Der Empfängermodus kann vom Versender gestoppt werden", - "gui_settings_public_mode_checkbox": "Öffentlich", + "gui_settings_public_mode_checkbox": "Öffentlicher Modus", "systray_close_server_title": "OnionShareServer gestoppt", "systray_close_server_message": "Ein Nutzer hat den Server gestoppt", "systray_download_page_loaded_message": "Ein Nutzer hat die Downloadseite geöffnet", @@ -169,5 +169,17 @@ "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", "gui_settings_language_label": "Bevorzugte Sprache", "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", - "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)" + "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)", + "timeout_upload_still_running": "Warte bis der Upload abgeschlossen wurde", + "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.", + "gui_settings_connection_type_bundled_option": "Benutzt die in OnionShare eingebaute Tor-Version", + "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", + "gui_server_started_after_timeout": "Der Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", + "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das teilen zu starten.", + "gui_status_indicator_share_stopped": "Bereit zum teilen", + "history_in_progress_tooltip": "{} läuft", + "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", + "systray_page_loaded_title": "OnionShare Seite geladen", + "gui_upload_finished_range": "{} hochgeladen zu {}", + "gui_upload_finished": "{} hochgeladen" } -- cgit v1.2.3-54-g00ecf From feaa360dcf73b40ea2a14c9350568bd31de550da Mon Sep 17 00:00:00 2001 From: Zuhualime Akoochimoya Date: Mon, 24 Dec 2018 09:42:54 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 90 ++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 131fb9be..93a86673 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -2,14 +2,14 @@ "preparing_files": "Comprimiendo los archivos.", "give_this_url": "Entrega esta URL al receptor:", "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", - "not_a_file": "{0:s} no es un archivo.", + "not_a_file": "{0:s} no es un archivo válido.", "other_page_loaded": "La URL está lista", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", "help_local_only": "No usar Tor (sólo para desarrollo)", "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastre\narchivos aquí", + "gui_drag_and_drop": "Arrastre y suelte archivos y carpetas\npara empezar a compartir", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", @@ -31,16 +31,16 @@ "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles", "gui_please_wait": "Comenzando.... Haz clic para cancelar.", "gui_quit_title": "No tan rápido", - "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarlo, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", + "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarla, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", "zip_progress_bar_format": "Comprimiendo: %p%", - "error_stealth_not_supported": "Para crear servicios de cebolla sigilosos, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare requiere al menos Tor 0.2.7.1 y al menos python3-stem 1.4.0.", + "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", - "gui_settings_stealth_hidservauth_string": "Después de haber guardado su clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", - "gui_settings_autoupdate_label": "Control para versión nueva", + "gui_settings_stealth_hidservauth_string": "Habiendo guardado su clave privada para volver a utilizarla, ahora puede\nhacer clic para copiar tu HidServAuth.", + "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", - "gui_settings_autoupdate_check_button": "Buscar una Nueva Versión", + "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare", "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor", "gui_settings_connection_type_test_button": "Probar la conexión a Tor", @@ -70,42 +70,42 @@ "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", - "settings_error_unknown": "No se puede conectar al controlador Tor porque la configuración no tiene sentido.", + "settings_error_unknown": "No se puede conectar al controlador Tor porque su configuración no tiene sentido.", "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", - "settings_error_auth": "Conectado a {}:{}, pero no se puede autentificar. ¿Quizás este no sea un controlador Tor?", + "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?", "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", - "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero no puedo autenticarme porque la contraseña parece estar equivocada, y tu usuario no tiene permiso para leer el archivo cookie.", - "settings_error_bundled_tor_not_supported": "Bundled Tor no sepuede usar si no se usa el modo de desarrollo en Windows o macOS.", - "settings_error_bundled_tor_timeout": "Conectarse con Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado, o el reloj no está en hora?", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o su usuario no tiene permiso para leer el archivo cookie.", + "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", + "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tiene un reloj impreciso?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímeros: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", + "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", - "error_invalid_private_key": "Este tipo de clave privada no es compatible", - "connecting_to_tor": "Conexión a la red Tor", - "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Br>Estás usando {} y el último es {}.", - "update_error_check_error": "No se han podido comprobar las nuevas versiones: El sitio web de OnionShare dice que la última versión es la irreconocible '{}'.…", - "update_error_invalid_latest_version": "No se ha podido comprobar la nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", - "update_not_available": "Estás ejecutando la última versión de OnionShare.", - "gui_tor_connection_ask": "Abrir la configuración para arrreglar la conexión a Tor?", - "gui_tor_connection_ask_open_settings": "sí", + "error_invalid_private_key": "Este tipo de clave privada no está soportado", + "connecting_to_tor": "Conectando a la red de Tor", + "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Estás usando {} y el último es {}.", + "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…", + "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no está conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Está ejecutando la última versión de OnionShare.", + "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?", + "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Salir", - "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", - "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configura tu conexión a Tor.", + "gui_tor_connection_error_settings": "Intente cambiando la forma en que OnionShare se conecta a la red Tor en su configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar su conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor, haz una nueva porción.", - "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", - "share_via_onionshare": "Compártelo con OnionShare", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor cree nueva conexión compartida.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualícelo para comenzar a compartir.", + "share_via_onionshare": "Compártalo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", - "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Tor Browser: ", - "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Tor Browser: ", - "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", + "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Navegador Tor: ", + "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", - "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_status_indicator_share_stopped": "Listo para compartir", "gui_status_indicator_share_working": "Comenzando.…", "gui_status_indicator_share_started": "Compartir", @@ -118,11 +118,11 @@ "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", "info_in_progress_uploads_tooltip": "{} subida(s) en curso", "info_completed_uploads_tooltip": "{} subida(s) completada(s)", - "receive_mode_downloads_dir": "Los archivos enviados a ti aparecen en esta carpeta: {}", - "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "receive_mode_downloads_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abre, podrían tomar el control de su ordenador. Abra sólo cosas de personas en las que confíe, o si sabe lo que está haciendo.", "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", - "gui_download_upload_progress_eta": "{0:s}, tiempo restante: {1:s}, %p%", + "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?", "gui_quit_warning_quit": "Salir", @@ -131,7 +131,7 @@ "gui_settings_autoupdate_timestamp": "Última comprobación: {}", "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_general_label": "Ajustes generales", - "gui_settings_sharing_label": "Configuración de uso compartido", + "gui_settings_sharing_label": "Configuración de compartición", "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", @@ -145,23 +145,23 @@ "gui_settings_password_label": "Contraseña", "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes", "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?", - "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes enchufables obfs4 incorporados", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes enchufables obfs4 incorporados (requiere obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes insertables obfs4 incorporados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes insertables obfs4 incorporados (requiere obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte insertable incorporado meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte insertable meek_lite (Azure) incorporado (requiere obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son muy costosos de correr para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", "gui_settings_button_save": "Guardar", "gui_settings_button_cancel": "Cancelar", "gui_settings_button_help": "Ayuda", "gui_settings_shutdown_timeout_checkbox": "Usar temporizador de parada automática", - "gui_settings_shutdown_timeout": "Detener el compartir en:", - "history_in_progress_tooltip": "{} En progreso", + "gui_settings_shutdown_timeout": "Detener carpeta compartida en:", + "history_in_progress_tooltip": "{} en progreso", "history_completed_tooltip": "{} completado", - "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta del modo de recepción: {}", + "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}", "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", - "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.


    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", "receive_mode_received_file": "Recibido: {}", "gui_mode_share_button": "Compartir archivos", @@ -184,7 +184,7 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", "timeout_upload_still_running": "Esperando a que se complete la subida" } -- cgit v1.2.3-54-g00ecf From e81b459f858cb85675d36fb96d7865c72eaa7ad2 Mon Sep 17 00:00:00 2001 From: Mattias Bertolino Date: Mon, 24 Dec 2018 14:18:48 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index 38ac4fa7..c5f6e8da 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -26,7 +26,7 @@ "systray_upload_started_message": "En användare började ladda upp filer på din dator", "help_local_only": "Använd inte Tor (endast för utveckling)", "help_stay_open": "Fortsätt dela efter första nedladdning", - "help_shutdown_timeout": "", + "help_shutdown_timeout": "Avbryt delning efter ett bestämt antal sekunder", "help_stealth": "Använd klient-auktorisering (avancerat)", "help_receive": "Ta emot delningar istället för att skicka dem", "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", @@ -51,7 +51,7 @@ "gui_canceled": "Avbruten", "gui_copied_url_title": "OnionShare Adress Kopierad", "gui_copied_url": "OnionShare adress kopierad till urklipp", - "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth_title": "HidServAuth Kopierad", "gui_copied_hidservauth": "", "gui_please_wait": "", "gui_download_upload_progress_complete": "", -- cgit v1.2.3-54-g00ecf From d0f8ff76a3e67be0730923880a6bf00aac37be30 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Dec 2018 09:10:47 -0800 Subject: Weblate merge (#856) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Added translation using Weblate (Catalan) * Added translation using Weblate (Georgian) * Added translation using Weblate (Greek) * Added translation using Weblate (Hungarian) * Added translation using Weblate (Icelandic) * Added translation using Weblate (Irish) * Added translation using Weblate (Persian) * Added translation using Weblate (Punjabi) * Added translation using Weblate (Romanian) * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Added translation using Weblate (Portuguese (Brazil)) * Added translation using Weblate (Portuguese (Portugal)) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Deleted translation using Weblate (Portuguese) * Added translation using Weblate (Arabic) * Added translation using Weblate (Indonesian) * Added translation using Weblate (Macedonian) * Added translation using Weblate (Polish) * Added translation using Weblate (Slovenian) * Added translation using Weblate (Tachelhit) * Added translation using Weblate (Wolof) * Added translation using Weblate (Chinese (Simplified)) * Added translation using Weblate (Chinese (Traditional)) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Irish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ga/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Korean) * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Bengali) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Gujarati) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/gu/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Esperanto) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/eo/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Georgian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ka/ * Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ * Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Macedonian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/mk/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Punjabi) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pa/ * Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Slovenian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sl/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Deleted translation using Weblate (Tachelhit) * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ * Update from Weblate (#4) * Translated using Weblate * Added translation using Weblate (Amharic) * Added translation using Weblate (Luganda) * Added translation using Weblate (Yoruba) * Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ * Translated using Weblate (Amharic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/am/ * Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Added translation using Weblate (Swedish) * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Added translation using Weblate (Bulgarian) * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/am.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/ar.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/bg.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/bn.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/ca.json | 186 +++++++++++++++++++++++++++++++++++++++++++ share/locale/cs.json | 6 +- share/locale/de.json | 42 ++++++---- share/locale/el.json | 186 +++++++++++++++++++++++++++++++++++++++++++ share/locale/eo.json | 12 +-- share/locale/es.json | 97 +++++++++++----------- share/locale/fa.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/fr.json | 42 ++++++++-- share/locale/gu.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/hu.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/id.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/is.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/it.json | 70 +++++++++++++++- share/locale/ka.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/ko.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/lg.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/mk.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/nl.json | 199 ++++++++++++++++++++++++++++++++-------------- share/locale/pa.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/pl.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/pt_BR.json | 4 +- share/locale/pt_PT.json | 42 +++++----- share/locale/ro.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/ru.json | 189 +++++++++++++++++++++++++++++++++++++++++-- share/locale/sl.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/sv.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/tr.json | 9 ++- share/locale/wo.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/yo.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/zh_Hans.json | 185 ++++++++++++++++++++++++++++++++++++++++++ share/locale/zh_Hant.json | 185 ++++++++++++++++++++++++++++++++++++++++++ 35 files changed, 4980 insertions(+), 174 deletions(-) create mode 100644 share/locale/am.json create mode 100644 share/locale/ar.json create mode 100644 share/locale/bg.json create mode 100644 share/locale/bn.json create mode 100644 share/locale/ca.json create mode 100644 share/locale/el.json create mode 100644 share/locale/fa.json create mode 100644 share/locale/gu.json create mode 100644 share/locale/hu.json create mode 100644 share/locale/id.json create mode 100644 share/locale/is.json create mode 100644 share/locale/ka.json create mode 100644 share/locale/ko.json create mode 100644 share/locale/lg.json create mode 100644 share/locale/mk.json create mode 100644 share/locale/pa.json create mode 100644 share/locale/pl.json create mode 100644 share/locale/ro.json create mode 100644 share/locale/sl.json create mode 100644 share/locale/sv.json create mode 100644 share/locale/wo.json create mode 100644 share/locale/yo.json create mode 100644 share/locale/zh_Hans.json create mode 100644 share/locale/zh_Hant.json diff --git a/share/locale/am.json b/share/locale/am.json new file mode 100644 index 00000000..d226d86e --- /dev/null +++ b/share/locale/am.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "ተወው", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "ተወው", + "gui_settings_button_help": "መመሪያ", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "አዎ", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/ar.json b/share/locale/ar.json new file mode 100644 index 00000000..4abe35e1 --- /dev/null +++ b/share/locale/ar.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", + "preparing_files": "ضغط الملفات", + "give_this_url": "أعط هذا العنوان للمستقبل", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "خروج", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "المستخدم الغاء التحميل", + "systray_upload_started_title": "onionshare تحميل بدأت", + "systray_upload_started_message": "بدأ المستخدم تحميل الملفات إلى جهاز الكمبيوتر الخاص بك", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "إضافة", + "gui_delete": "حذف", + "gui_choose_items": "إختر", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "ألغى", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "خروج", + "gui_quit_warning_dont_quit": "إلغاء", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "الإعدادات", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "التحقق من الإصدار الجديد", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "أبدا", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "الإعدادات العامة", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "كلمة السر", + "gui_settings_password_label": "كلمة السر", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "حفظ", + "gui_settings_button_cancel": "إلغاء", + "gui_settings_button_help": "مساعدة", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "نعم,", + "gui_tor_connection_ask_quit": "خروج", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "جاري الإستلام", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "استعراض", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/bg.json b/share/locale/bg.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/bg.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/bn.json b/share/locale/bn.json new file mode 100644 index 00000000..e62db7da --- /dev/null +++ b/share/locale/bn.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "প্রস্থান করুন", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "সংযোজন করুন", + "gui_delete": "মুছ", + "gui_choose_items": "পছন্দ করুন", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "প্রস্থান করুন", + "gui_quit_warning_dont_quit": "বাতিল", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "সেটিং", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "নিয়ন্ত্রন পোর্ট", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "পাসওয়ার্ড", + "gui_settings_password_label": "পাসওয়ার্ড", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "সেভ", + "gui_settings_button_cancel": "বাতিল", + "gui_settings_button_help": "সাহায্য", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "হ্যাঁ", + "gui_tor_connection_ask_quit": "প্রস্থান করুন", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "দেখা", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/ca.json b/share/locale/ca.json new file mode 100644 index 00000000..068ed541 --- /dev/null +++ b/share/locale/ca.json @@ -0,0 +1,186 @@ +{ + "config_onion_service": "S'està establint el servei onion al port {0:d}.", + "preparing_files": "S'estan comprimint els arxius.", + "give_this_url": "Dóna aquesta adreça a la persona destinatària:", + "give_this_url_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona destinatària:", + "give_this_url_receive": "Dóna aquesta adreça a la persona remitent:", + "give_this_url_receive_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona remitent:", + "ctrlc_to_stop": "Prem Control+C per aturar el servidor", + "not_a_file": "{0:s} no és un arxiu vàlid.", + "not_a_readable_file": "{0:s} no és un arxiu llegible.", + "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei onion", + "other_page_loaded": "Adreça carregada", + "close_on_timeout": "S'ha aturat perquè s'ha acabat el temps d'espera", + "closing_automatically": "S'ha aturat perquè ha acabat la descàrrega", + "timeout_download_still_running": "S'està esperant que acabi la descàrrega", + "large_filesize": "Compte: La transferència d'arxius molt grans podria trigar hores", + "systray_menu_exit": "Surt", + "systray_download_started_title": "S'ha iniciat la descàrrega amb OnionShare", + "systray_download_started_message": "Algú ha començat a descarregar els teus arxius", + "systray_download_completed_title": "S'ha completat la descàrrega amb OnionShare", + "systray_download_completed_message": "Algú ha acabat de descarregar els teus arxius", + "systray_download_canceled_title": "S'ha canceŀlat la descàrrega", + "systray_download_canceled_message": "L'usuari va cancel·lar la descàrrega", + "systray_upload_started_title": "S'ha iniciat la pujada", + "systray_upload_started_message": "Algú ha començat a pujar arxius al teu ordinador", + "help_local_only": "No facis servir Tor (només per a desenvolupament)", + "help_stay_open": "Manté obert el servei després de la primera descàrrega", + "help_shutdown_timeout": "Deixa de compartir al cap de tants segons", + "help_stealth": "Fes servir autorització de client (avançat)", + "help_receive": "Rep recursos en comptes d'enviar-los", + "help_debug": "Envia els errors d'OnionShare a stdout i els errors web al disc", + "help_filename": "Llista d'arxius o carpetes a compartir", + "help_config": "Ubicació de la configuració JSON personalitzada", + "gui_drag_and_drop": "Arrossega arxius i carpetes\nper començar a compartir", + "gui_add": "Afegeix", + "gui_delete": "Esborra", + "gui_choose_items": "Escull", + "gui_share_start_server": "Comparteix", + "gui_share_stop_server": "Deixa de compartir", + "gui_share_stop_server_shutdown_timeout": "Deixa de compartir (queden {}s)", + "gui_share_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}", + "gui_receive_start_server": "Inicia en mode de recepció", + "gui_receive_stop_server": "Atura el mode de recepció", + "gui_receive_stop_server_shutdown_timeout": "Atura el mode de recepció (queden {}s)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "El temporitzador acaba a {}", + "gui_copy_url": "Copia l'adreça", + "gui_copy_hidservauth": "Copia el HidServAuth", + "gui_downloads": "Historial de descàrregues", + "gui_no_downloads": "No n'hi ha cap", + "gui_canceled": "Canceŀlat", + "gui_copied_url_title": "S'ha copiat l'adreça OnionShare", + "gui_copied_url": "S'ha copiat l'adreça OnionShare al porta-retalls", + "gui_copied_hidservauth_title": "S'ha copiat el HidServAuth", + "gui_copied_hidservauth": "S'ha copiat la línia HidServAuth al porta-retalls", + "gui_please_wait": "S'està iniciant… Clica per a canceŀlar.", + "gui_download_upload_progress_complete": "Han passat %p%, {0:s}.", + "gui_download_upload_progress_starting": "{0:s}, %p% (s'està calculant)", + "gui_download_upload_progress_eta": "{0:s}, temps restant: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Espera un moment", + "gui_share_quit_warning": "Encara s'estan enviant fitxers. Segur que vols sortir de l'OnionShare?", + "gui_receive_quit_warning": "Encara s'estan rebent arxius. Segur que vols sortir de l'OnionShare?", + "gui_quit_warning_quit": "Surt", + "gui_quit_warning_dont_quit": "Canceŀla", + "error_rate_limit": "Algú ha fet massa intents a la teva adreça, cosa que podria significar que l'estan intentant endevinar. Per això OnionShare s'ha aturat sola. Pots tornar a començar i enviar a la destinatària la nova adreça.", + "zip_progress_bar_format": "S'està comprimint: %p%", + "error_stealth_not_supported": "Per fer servir l'autorització de client, necessites les versions iguals o superiors a Tor 0.2.9.1-alpha (o Tor Browser 6.5) i python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare necessita almenys les versions Tor 0.2.7.1 i python3-stem 1.4.0.", + "gui_settings_window_title": "Configuració", + "gui_settings_whats_this": "Què és això?", + "gui_settings_stealth_option": "Fes servir autorització de client (antiquada)", + "gui_settings_stealth_hidservauth_string": "Ara que has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu HidServAuth.", + "gui_settings_autoupdate_label": "Comprova si hi ha noves versions", + "gui_settings_autoupdate_option": "Notifica'm si hi ha una actualització disponible", + "gui_settings_autoupdate_timestamp": "Última comprovació: {}", + "gui_settings_autoupdate_timestamp_never": "Mai", + "gui_settings_autoupdate_check_button": "Comprova si hi ha una versió més nova", + "gui_settings_general_label": "Configuració general", + "gui_settings_sharing_label": "Configuració de compartir", + "gui_settings_close_after_first_download_option": "Deixa de compartir després de la primera descàrrega", + "gui_settings_connection_type_label": "Com hauria de connectar-se OnionShare a Tor?", + "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare", + "gui_settings_connection_type_automatic_option": "Intenta la configuració automàtica amb el Navegador Tor", + "gui_settings_connection_type_control_port_option": "Connecta fent servir el port de control", + "gui_settings_connection_type_socket_file_option": "Connecta fent servir un arxiu de socket", + "gui_settings_connection_type_test_button": "Comprova la connexió a Tor", + "gui_settings_control_port_label": "Port de control", + "gui_settings_socket_file_label": "Arxiu de socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Configuració d'autenticació a Tor", + "gui_settings_authenticate_no_auth_option": "Sense autenticació o autenticació per cookies", + "gui_settings_authenticate_password_option": "Contrasenya", + "gui_settings_password_label": "Contrasenya", + "gui_settings_tor_bridges": "Ponts de Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "No facis servir ponts", + "gui_settings_tor_bridges_obfs4_radio_option": "Fes servir el transport connectable obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Fes servir el transport connectable obfs4 (necessita obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Fes servir el transport connectable meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Fes servir el transport connectable meek_lite (Azure, necessita obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Compte: els ponts meek_lite costen molts recursos al Tor Project per funcionar.

    Sisplau, fes-los servir només si no pots connectar-te a Tor directament, a través de obfs4, o a través de ponts normals.", + "gui_settings_tor_bridges_custom_radio_option": "Fes servir ponts personalitzats", + "gui_settings_tor_bridges_custom_label": "Pots trobar-ne a https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Cap dels ponts que has afegit ha funcionat.\nComprova'ls o prova d'afegir-ne de nous.", + "gui_settings_button_save": "Desa", + "gui_settings_button_cancel": "Canceŀla", + "gui_settings_button_help": "Ajuda", + "gui_settings_shutdown_timeout_checkbox": "Posa un temporitzador d'aturada", + "gui_settings_shutdown_timeout": "Atura a:", + "settings_error_unknown": "No s'ha pogut connectar a Tor perquè la configuració és inconsistent.", + "settings_error_automatic": "No s'ha pogut connectar al controlador de Tor. Tens el navegador de Tor arrencat? (el pots descarregar a torproject.org)", + "settings_error_socket_port": "No s'ha pogut establir la connexió al controlador de Tor a {}:{}.", + "settings_error_socket_file": "No s'ha pogut connectar al controlador de Tor fent servir el fitxer de socket {}.", + "settings_error_auth": "S'ha establert la connexió a {}:{} però ha fallat l'autenticació. Pot ser que no sigui un controlador de Tor?", + "settings_error_missing_password": "S'ha establer la connexió al controlador de Tor, però necessita una contrasenya d'autenticació.", + "settings_error_unreadable_cookie_file": "S'ha establert la connexió al controlador de Tor, però hi ha hagut un error de permisos. Pot ser que la contrasenya sigui errònia o que faltin permisos de lectura a l'arxiu de cookie.", + "settings_error_bundled_tor_not_supported": "La versió de Tor inclosa a OnionShare no funciona en mode de desenvolupador a Windows ni MacOS.", + "settings_error_bundled_tor_timeout": "Està trigant molt la connexió. Assegura't que estàs connectat a internet i que tens en hora el rellotge del sistema.", + "settings_error_bundled_tor_broken": "OnionShare no s'ha pogut connectar a Tor en segon pla:\n{}", + "settings_test_success": "Connectat al controlador de Tor.\n\nVersió de Tor: {}\nSuporta serveis onion efímers: {}.\nSuporta autenticació del client: {}.\nSuporta adreces .onion de nova generació: {}.", + "error_tor_protocol_error": "Hi ha hagut un error amb Tor: {}", + "error_tor_protocol_error_unknown": "Hi ha hagut un error desconegut amb Tor", + "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", + "connecting_to_tor": "Connectant a la xarxa Tor", + "update_available": "Ha sortit una nova versió d'OnionShare.Feu clic aquí per obtenir-la.

    Esteu usant {} i la més recent és {}.", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "S'està esperant que acabi la pujada" +} diff --git a/share/locale/cs.json b/share/locale/cs.json index a595ce67..74eb8ea3 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -3,7 +3,7 @@ "preparing_files": "Připravuji soubory ke sdílení.", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", + "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", @@ -40,8 +40,8 @@ "gui_settings_window_title": "Nastavení", "gui_settings_connection_type_label": "Jak by se měl OnionShare připojit k Toru?", "gui_settings_connection_type_automatic_option": "Zkusit automatické nastavení s Tor Browserem", - "gui_settings_connection_type_control_port_option": "Connect using control port", - "gui_settings_connection_type_socket_file_option": "Connect using socket file", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", "gui_settings_control_port_label": "Control port", "gui_settings_socket_file_label": "Socket file", "gui_settings_authenticate_label": "Autentizační možnosti Toru", diff --git a/share/locale/de.json b/share/locale/de.json index 30f5eed0..3aae1951 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -92,7 +92,7 @@ "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", - "zip_progress_bar_format": "Packe: %p%", + "zip_progress_bar_format": "Komprimiere: %p%", "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", @@ -101,29 +101,29 @@ "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", "gui_settings_general_label": "Allgemeine Einstellungen", - "gui_settings_sharing_label": "Servereinstellungen", - "gui_settings_connection_type_automatic_option": "Versuche mit dem Tor Browser automatisch zu konfigurieren", + "gui_settings_sharing_label": "Freigabe-Einstellungen", + "gui_settings_connection_type_automatic_option": "Versuche auto-konfiguartion mittels des Tor Browsers", "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", - "gui_settings_tor_bridges": "Einstellungen für Tor bridges", + "gui_settings_tor_bridges": "Unterstützung für Tor Bridges", "gui_settings_meek_lite_expensive_warning": "Achtung: Die meek_lite bridges sind für das Tor Projekt sehr kostspielig.

    Nutze sie nur, wenn du dich nicht direkt, per obfs4 Transport oder über andere, normale bridges zum Tornetzwerk verbinden kannst.", "gui_settings_tor_bridges_invalid": "Keine der bridges, die du angegeben hast, funktionieren.\nÜberprüfe sie oder gebe Andere an.", "settings_error_unknown": "Kann nicht zum Tor controller verbinden, weil deine Einstellungen keinen Sinn ergeben.", "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", - "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer hat keine Rechte, die Cookiedatei zu lesen.", - "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare gekoppelt ist nicht nutzen.", + "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer darf die Cookiedatei nicht lesen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", - "settings_error_bundled_tor_broken": "OnionShare konnte nicht zu Tor im Hintergrund verbinden:\n{}", - "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion Adressen der nächsten Generation: {}.", + "settings_error_bundled_tor_broken": "OnionShare konnte in Hintergrund nicht mit Tor verbinden:\n{}", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Klient-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", - "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShareSeite sagt, die letzte Version ist die unkenntliche '{}'…", + "update_error_check_error": "Konnte nicht nach neueren Versionen suchen: Die OnionShare-Seite sagt, die aktuelle Version ist nicht wiederzuerkennen '{}'…", "update_error_invalid_latest_version": "Konnte nicht nach neueren Versionen suchen: Bist du vielleicht nicht mit dem Tornetzwerk verbunden oder ist die OnionShareSeite offline?", "update_not_available": "Du benutzt bereits die neueste Version von OnionShare.", - "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk zu ermöglichen?", + "gui_tor_connection_ask": "Einstellungen öffnen, um die Verbindung zum Tornetzwerk in Ordnung zu bringen?", "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_error_settings": "Versuche in den Einstellung zu ändern, wie sich OnionShare mit dem Tornetzwerk verbindet.", "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", @@ -136,13 +136,13 @@ "gui_url_label_stay_open": "Dieser Server wird nicht automatisch stoppen.", "gui_url_label_onetime": "Dieser Server wird nach dem ersten vollständigen Download stoppen.", "gui_status_indicator_share_working": "Starte…", - "gui_status_indicator_share_started": "Läuft", + "gui_status_indicator_share_started": "Teilen", "gui_status_indicator_receive_stopped": "Bereit zum Empfangen", "gui_status_indicator_receive_working": "Starte…", "gui_status_indicator_receive_started": "Empfange", "gui_file_info": "{} Dateien, {}", "gui_file_info_single": "{} Datei, {}", - "history_completed_tooltip": "{} vollständige", + "history_completed_tooltip": "{} abgeschlossen", "info_in_progress_uploads_tooltip": "{} Upload(s) laufen", "info_completed_uploads_tooltip": "{} Upload(s) vollständig", "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", @@ -152,11 +152,11 @@ "receive_mode_received_file": "Empfangen: {}", "gui_mode_share_button": "Versende Dateien", "gui_mode_receive_button": "Empfange Dateien", - "gui_settings_receiving_label": "Empfangseinstellungen", + "gui_settings_receiving_label": "Empfangs-Einstellungen", "gui_settings_downloads_label": "Speichere Dateien in", "gui_settings_downloads_button": "Durchsuchen", "gui_settings_receive_allow_receiver_shutdown_checkbox": "Der Empfängermodus kann vom Versender gestoppt werden", - "gui_settings_public_mode_checkbox": "Öffentlich", + "gui_settings_public_mode_checkbox": "Öffentlicher Modus", "systray_close_server_title": "OnionShareServer gestoppt", "systray_close_server_message": "Ein Nutzer hat den Server gestoppt", "systray_download_page_loaded_message": "Ein Nutzer hat die Downloadseite geöffnet", @@ -169,5 +169,17 @@ "gui_open_folder_error_nautilus": "Kann den Ordner nicht öffnen, weil Nautilus nicht verfügbar ist. Die Datei ist hier: {}", "gui_settings_language_label": "Bevorzugte Sprache", "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", - "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)" + "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)", + "timeout_upload_still_running": "Warte bis der Upload abgeschlossen wurde", + "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.", + "gui_settings_connection_type_bundled_option": "Benutzt die in OnionShare eingebaute Tor-Version", + "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", + "gui_server_started_after_timeout": "Der Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", + "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das teilen zu starten.", + "gui_status_indicator_share_stopped": "Bereit zum teilen", + "history_in_progress_tooltip": "{} läuft", + "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", + "systray_page_loaded_title": "OnionShare Seite geladen", + "gui_upload_finished_range": "{} hochgeladen zu {}", + "gui_upload_finished": "{} hochgeladen" } diff --git a/share/locale/el.json b/share/locale/el.json new file mode 100644 index 00000000..dcdc97f1 --- /dev/null +++ b/share/locale/el.json @@ -0,0 +1,186 @@ +{ + "config_onion_service": "Δημιουργία onion service στην πύλη {0:d}.", + "preparing_files": "Συμπίεση αρχείων.", + "give_this_url": "Δώσε αυτή την διεύθυνση στον/στην παραλήπτη/τρια:", + "give_this_url_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και τη σειρά HidServAuth ως παραλήπτη:", + "give_this_url_receive": "Δώσε αυτή τη διεύθυνση στον/στην αποστολέα:", + "give_this_url_receive_stealth": "Συμπληρώστε αυτήν τη διεύθυνση και το HidServAuth ως αποστολέα:", + "ctrlc_to_stop": "Πάτα Ctrl+C για να σταματήσεις το σέρβερ", + "not_a_file": "{0:s} δεν είναι έγκυρο αρχείο.", + "not_a_readable_file": "Το {0:s} δεν είναι αναγνώσιμο αρχείο.", + "no_available_port": "Δεν βρέθηκε διαθέσιμη θύρα για να ξεκινήσει η υπηρεσία onion", + "other_page_loaded": "Η διεύθυνση φορτώθηκε", + "close_on_timeout": "Τερματίστηκε γιατί το χρονόμετρο τερματισμού έφτασε στο τέλος", + "closing_automatically": "Τερματίστηκε επειδή η λήψη ολοκληρώθηκε", + "timeout_download_still_running": "Αναμονή ολοκλήρωσης της λήψης", + "large_filesize": "Προειδοποίηση: Η αποστολή μεγάλου όγκου δεδομένων μπορεί να διαρκέσει ώρες", + "systray_menu_exit": "Έξοδος", + "systray_download_started_title": "Η λήψη του OnionShare ξεκίνησε", + "systray_download_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να κατεβάζει τα αρχεία σου", + "systray_download_completed_title": "Η λήψη του OnionShare ολοκληρώθηκε", + "systray_download_completed_message": "Ο/η χρήστης/τρια ολοκλήρωσε την λήψη των αρχείων σου", + "systray_download_canceled_title": "Η λήψη του OnionShare ακυρώθηκε", + "systray_download_canceled_message": "Ο/η χρήστης/τρια ακύρωσε τη λήψη", + "systray_upload_started_title": "Η λήψη του OnionShare ξεκίνησε", + "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου", + "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", + "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά τη πρώτη λήψη", + "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", + "help_stealth": "Χρησιμοποιήστε άδεια χρήστη (Για προχωρημένους)", + "help_receive": "", + "help_debug": "", + "help_filename": "Λίστα αρχείων ή φακέλων για μοίρασμα", + "help_config": "", + "gui_drag_and_drop": "Σύρτε και αφήστε αρχεία και φακέλους\nγια να αρχίσετε να τα μοιράζεστε", + "gui_add": "Προσθήκη", + "gui_delete": "Διαγραφή", + "gui_choose_items": "Επιλογή", + "gui_share_start_server": "Εκκίνηση μοιράσματος", + "gui_share_stop_server": "Τερματισμός μοιράσματος", + "gui_share_stop_server_shutdown_timeout": "Τερματισμός μοιράσματος (απομένουν {}\")", + "gui_share_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", + "gui_receive_start_server": "Εκκίνηση κατάστασης λήψης", + "gui_receive_stop_server": "Τερματισμός κατάστασης λήψης", + "gui_receive_stop_server_shutdown_timeout": "Τερματισμός κατάστασης λήψης (υπολοίπονται {}\")", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Το χρονόμετρο αυτόματου τερματισμού τελειώνει σε {}", + "gui_copy_url": "Αντιγραφή διεύθυνσης", + "gui_copy_hidservauth": "Αντιγραφή HidServAuth", + "gui_downloads": "Ιστορικό Λήψεων", + "gui_no_downloads": "Καμία λήψη ως τώρα", + "gui_canceled": "Ακυρώθηκε", + "gui_copied_url_title": "Αντεγραμμένη διεύθυνση OnionShare", + "gui_copied_url": "Αντεγράφη η διεύθυνση OnionShare στον πίνακα", + "gui_copied_hidservauth_title": "Αντιγραμμένος HidServAuth", + "gui_copied_hidservauth": "Η σειρά HidServAuth αντεγράφη στον πίνακα", + "gui_please_wait": "Ξεκινάμε... Κάντε κλικ για ακύρωση.", + "gui_download_upload_progress_complete": "%p%, {0:s} πέρασαν.", + "gui_download_upload_progress_starting": "{0:s}, %p% (υπολογισμός)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Όχι τόσο γρήγορα", + "gui_share_quit_warning": "Είστε στη διαδικασία αποστολής αρχείων. Είστε σίγουρος πως θέλετε να ακυρώσετε το OnionShare?", + "gui_receive_quit_warning": "Είστε στη διαδικασία παραλαβής αρχείων. Είστε σίγουρος πώς θέλετε να ακυρώσετε το OnionShare?", + "gui_quit_warning_quit": "Έξοδος", + "gui_quit_warning_dont_quit": "Ακύρωση", + "error_rate_limit": "Κάποιος προσπάθησε επανειλημμένα να μπει στη διεύθυνσή σας, το οποίο σημαίνει πως μπορεί να προσπαθεί να την μαντέψει, οπότε το OnionShare σταμάτησε τον server. Ξεκινήστε πάλι το μοίρασμα και στείλτε στον παραλήπτη μία νέα διεύθυνση για κοινοποίηση.", + "zip_progress_bar_format": "Συμπίεση: %p%", + "error_stealth_not_supported": "Για τη χρήση άδειας χρήστη, χρειάζεστε τουλάχιστον το Tor 0.2.9.1-alpha (ή τον Tor Browser 6.5) και το python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", + "gui_settings_window_title": "Ρυθμίσεις", + "gui_settings_whats_this": " Τί είναι αυτό? ", + "gui_settings_stealth_option": "Χρήση άδειας χρήστη (αδειοδότηση)", + "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα \nνα επιλέξετε την αντιγραφή του HidServAuth σας.", + "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", + "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", + "gui_settings_autoupdate_timestamp": "Τελευταίος έλεγχος: {}", + "gui_settings_autoupdate_timestamp_never": "Ποτέ", + "gui_settings_autoupdate_check_button": "Έλεγχος για νέα έκδοση", + "gui_settings_general_label": "Γενικές ρυθμίσεις", + "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης", + "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης μετά την πρώτη λήψη", + "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?", + "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare", + "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser", + "gui_settings_connection_type_control_port_option": "Σύνδεση μέσω πύλης ελέγχου", + "gui_settings_connection_type_socket_file_option": "Σύνδεση μέσω αρχείου μετάβασης", + "gui_settings_connection_type_test_button": "Έλεγχος της σύνδεσης με το Tor", + "gui_settings_control_port_label": "Πύλη ελέγχου", + "gui_settings_socket_file_label": "Αρχείο μετάβασης", + "gui_settings_socks_label": "πύλη SOCKS", + "gui_settings_authenticate_label": "Ρυθμίσεις επαλήθευσης Tor", + "gui_settings_authenticate_no_auth_option": "Καμία επαλήθευση ή επαλήθευση cookie", + "gui_settings_authenticate_password_option": "Κωδικός", + "gui_settings_password_label": "Κωδικός", + "gui_settings_tor_bridges": "Στήριξη Tor bridge", + "gui_settings_tor_bridges_no_bridges_radio_option": "Μην χρησιμοποιείτε bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Χρησιμοποιήστε ενσωματωμένα obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Χρησμοποιήστε ενσωματωμένα obfs4 pluggable transports (απαιτείται obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Χρησιμοποιήστε ενσωματωμένα meek_lite (Azure) pluggable transports (απαιτεί obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Προσοχή: Τα meek_lite bridges επιβαρύνουν πολύ το Tor Project στη λειτουργία.

    Χρησιμοποιήστε τα μόνο αν δεν μπορείτε να συνδεθείτε κατ' ευθείαν στο Tor μέσω obfs4 transports ή άλλα κανονικά bridges.", + "gui_settings_tor_bridges_custom_radio_option": "Χρήση κανονικών bridges", + "gui_settings_tor_bridges_custom_label": "Αποκτήστε bridges στο https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Δεν λειτούργησε κάποιο από τα bridges που προσθέσατε.\nΞαναελέγξτε τα ή επιλέξτε άλλα.", + "gui_settings_button_save": "Αποθήκευση", + "gui_settings_button_cancel": "Ακύρωση", + "gui_settings_button_help": "Βοήθεια", + "gui_settings_shutdown_timeout_checkbox": "Χρήση χρονομέτρου αυτόματου τερματισμού", + "gui_settings_shutdown_timeout": "Τερματισμός της κοινοποίησης στα:", + "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.", + "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?", + "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος" +} diff --git a/share/locale/eo.json b/share/locale/eo.json index 9902e4ae..bf578276 100644 --- a/share/locale/eo.json +++ b/share/locale/eo.json @@ -3,9 +3,9 @@ "preparing_files": "Preparas dosierojn por kundivido.", "give_this_url": "Donu ĉi tiun URL al la persono al kiu vi sendas la dosieron:", "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", + "ctrlc_to_stop": "Presu Ctrl-C por halti la servilon", "not_a_file": "{0:s} ne estas dosiero.", - "other_page_loaded": "URL loaded", + "other_page_loaded": "", "closing_automatically": "Haltas aŭtomate ĉar la elŝuto finiĝis", "large_filesize": "Atentigo: Sendado de grandaj dosieroj povas daŭri horojn", "help_local_only": "Ne strebu uzi tor: nur por evoluado", @@ -37,7 +37,7 @@ "zip_progress_bar_format": "Compressing files: %p%", "error_stealth_not_supported": "To create stealth onion services, you need at least Tor 0.2.9.1-alpha (or Tor Browser 6.5) and at least python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare postulas almenaŭ Tor 0.2.7.1 kaj almenaŭ python3-stem 1.4.0.", - "gui_settings_window_title": "Settings", + "gui_settings_window_title": "", "gui_settings_connection_type_label": "Kiel OnionShare devus konektiĝi al Tor?", "gui_settings_connection_type_automatic_option": "Provi aŭtomate agordi kun Tor Browser", "gui_settings_connection_type_control_port_option": "Konekti per kontrolpordo", @@ -45,7 +45,7 @@ "gui_settings_control_port_label": "Kontrolpordo", "gui_settings_socket_file_label": "Socket-dosiero", "gui_settings_authenticate_label": "Tor authentication options", - "gui_settings_authenticate_no_auth_option": "No authentication, or cookie authentication", + "gui_settings_authenticate_no_auth_option": "", "gui_settings_authenticate_password_option": "Pasvorto", "gui_settings_password_label": "Pasvorto", "gui_settings_button_save": "Konservi", @@ -55,8 +55,8 @@ "settings_error_automatic": "Ne eblas konektiĝi al Tor-kontrolilo. Ĉu Tor Browser funkcias en la fono? Se vi ne havas ĝin, vi povas ekhavi ĝin je:\nhttps://www.torproject.org/.", "settings_error_socket_port": "Ne eblas konektiĝi al Tor-kontrolilo je {}:{}.", "settings_error_socket_file": "Ne eblas konektiĝi al Tor-kontrolilo per socket-dosiero {}.", - "settings_error_auth": "Connected to {}:{}, but can't authenticate. Maybe this isn't a Tor controller?", - "settings_error_missing_password": "Connected to Tor controller, but it requires a password to authenticate.", + "settings_error_auth": "", + "settings_error_missing_password": "", "settings_error_unreadable_cookie_file": "Connected to Tor controller, but can't authenticate because your password may be wrong, and your user doesn't have permission to read the cookie file.", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work." diff --git a/share/locale/es.json b/share/locale/es.json index 3477fd3b..93a86673 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -2,14 +2,14 @@ "preparing_files": "Comprimiendo los archivos.", "give_this_url": "Entrega esta URL al receptor:", "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", - "not_a_file": "{0:s} no es un archivo.", + "not_a_file": "{0:s} no es un archivo válido.", "other_page_loaded": "La URL está lista", "closing_automatically": "Apagando automáticamente porque la descarga finalizó", "help_local_only": "No usar Tor (sólo para desarrollo)", "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastre\narchivos aquí", + "gui_drag_and_drop": "Arrastre y suelte archivos y carpetas\npara empezar a compartir", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", @@ -31,16 +31,16 @@ "gui_copied_hidservauth": "Línea HidServAuth copiada al portapapeles", "gui_please_wait": "Comenzando.... Haz clic para cancelar.", "gui_quit_title": "No tan rápido", - "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarlo, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", + "error_rate_limit": "Alguien ha hecho demasiados intentos equivocados en tu dirección, lo que significa que podrían estar intentando adivinarla, así que OnionShare ha detenido el servidor. Comienza a compartir de nuevo y envía al destinatario la nueva dirección para compartir.", "zip_progress_bar_format": "Comprimiendo: %p%", - "error_stealth_not_supported": "Para crear servicios de cebolla sigilosos, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", - "error_ephemeral_not_supported": "OnionShare requiere al menos Tor 0.2.7.1 y al menos python3-stem 1.4.0.", + "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", - "gui_settings_stealth_hidservauth_string": "Después de haber guardado su clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", - "gui_settings_autoupdate_label": "Control para versión nueva", + "gui_settings_stealth_hidservauth_string": "Habiendo guardado su clave privada para volver a utilizarla, ahora puede\nhacer clic para copiar tu HidServAuth.", + "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", - "gui_settings_autoupdate_check_button": "Buscar una Nueva Versión", + "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", "gui_settings_connection_type_bundled_option": "Use la versión Tor incorporada en OnionShare", "gui_settings_connection_type_automatic_option": "Intentar la autoconfiguración con el Navegador Tor", "gui_settings_connection_type_test_button": "Probar la conexión a Tor", @@ -70,42 +70,42 @@ "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", - "settings_error_unknown": "No se puede conectar al controlador Tor porque la configuración no tiene sentido.", + "settings_error_unknown": "No se puede conectar al controlador Tor porque su configuración no tiene sentido.", "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", - "settings_error_auth": "Conectado a {}:{}, pero no se puede autentificar. ¿Quizás este no sea un controlador Tor?", + "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?", "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", - "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero no puedo autenticarme porque la contraseña parece estar equivocada, y tu usuario no tiene permiso para leer el archivo cookie.", - "settings_error_bundled_tor_not_supported": "Bundled Tor no sepuede usar si no se usa el modo de desarrollo en Windows o macOS.", - "settings_error_bundled_tor_timeout": "Conectarse con Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado, o el reloj no está en hora?", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o su usuario no tiene permiso para leer el archivo cookie.", + "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", + "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tiene un reloj impreciso?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al *Tor controlador.\n\n*Tor Versión: {}\nSoporte servicios de cebolla efímeros: {}.\nAutentificación de cliente de los soportes: {}.\nSoporte direcciones de cebolla de nueva generación: {}.", + "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", - "error_invalid_private_key": "Este tipo de clave privada no es compatible", - "connecting_to_tor": "Conexión a la red Tor", - "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Br>Estás usando {} y el último es {}.", - "update_error_check_error": "No se han podido comprobar las nuevas versiones: El sitio web de OnionShare dice que la última versión es la irreconocible '{}'.…", - "update_error_invalid_latest_version": "No se ha podido comprobar la nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", - "update_not_available": "Estás ejecutando la última versión de OnionShare.", - "gui_tor_connection_ask": "Abrir la configuración para arrreglar la conexión a Tor?", - "gui_tor_connection_ask_open_settings": "sí", + "error_invalid_private_key": "Este tipo de clave privada no está soportado", + "connecting_to_tor": "Conectando a la red de Tor", + "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Estás usando {} y el último es {}.", + "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…", + "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no está conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Está ejecutando la última versión de OnionShare.", + "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?", + "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Salir", - "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", - "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configura tu conexión a Tor.", + "gui_tor_connection_error_settings": "Intente cambiando la forma en que OnionShare se conecta a la red Tor en su configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar su conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor, haz una nueva porción.", - "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", - "share_via_onionshare": "Compártelo con OnionShare", - "gui_use_legacy_v2_onions_checkbox": "Usar direcciones de legado", - "gui_save_private_key_checkbox": "Usar una dirección persistente (legado)", - "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Tor Browser: ", - "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Tor Browser: ", - "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor cree nueva conexión compartida.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualícelo para comenzar a compartir.", + "share_via_onionshare": "Compártalo con OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", + "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", + "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", + "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Navegador Tor: ", + "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", - "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_status_indicator_share_stopped": "Listo para compartir", "gui_status_indicator_share_working": "Comenzando.…", "gui_status_indicator_share_started": "Compartir", @@ -118,11 +118,11 @@ "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", "info_in_progress_uploads_tooltip": "{} subida(s) en curso", "info_completed_uploads_tooltip": "{} subida(s) completada(s)", - "receive_mode_downloads_dir": "Los archivos enviados a ti aparecen en esta carpeta: {}", - "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "receive_mode_downloads_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abre, podrían tomar el control de su ordenador. Abra sólo cosas de personas en las que confíe, o si sabe lo que está haciendo.", "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", - "gui_download_upload_progress_eta": "{0:s}, tiempo restante: {1:s}, %p%", + "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Estás enviando archivos. ¿Quieres realmente cerrar OnionShare?", "gui_quit_warning_quit": "Salir", @@ -131,7 +131,7 @@ "gui_settings_autoupdate_timestamp": "Última comprobación: {}", "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_general_label": "Ajustes generales", - "gui_settings_sharing_label": "Configuración de uso compartido", + "gui_settings_sharing_label": "Configuración de compartición", "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", @@ -145,23 +145,23 @@ "gui_settings_password_label": "Contraseña", "gui_settings_tor_bridges_no_bridges_radio_option": "No usar puentes", "gui_receive_quit_warning": "Estás recibiendo archivos. ¿Quieres cerrar OnionShare igualmente?", - "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes enchufables obfs4 incorporados", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes enchufables obfs4 incorporados (requiere obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte enchufable incorporado meek_lite (Azure)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte enchufable meek_lite (Azure) incorporado (requiere obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son caros para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", + "gui_settings_tor_bridges_obfs4_radio_option": "Usar transportes insertables obfs4 incorporados", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usar transportes insertables obfs4 incorporados (requiere obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utilizar transporte insertable incorporado meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usar transporte insertable meek_lite (Azure) incorporado (requiere obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Advertencia: Los puentes meek_lite son muy costosos de correr para el Proyecto Tor.

    Úsalos sólo si no puedes conectarte a Tor directamente, a través de transportes obfs4, u otros puentes normales.", "gui_settings_tor_bridges_custom_radio_option": "Usar puentes personalizados", "gui_settings_tor_bridges_custom_label": "Puedes obtener puentes en https://bridges.torproject.org", "gui_settings_button_save": "Guardar", "gui_settings_button_cancel": "Cancelar", "gui_settings_button_help": "Ayuda", "gui_settings_shutdown_timeout_checkbox": "Usar temporizador de parada automática", - "gui_settings_shutdown_timeout": "Detener el compartir en:", - "history_in_progress_tooltip": "{} En progreso", + "gui_settings_shutdown_timeout": "Detener carpeta compartida en:", + "history_in_progress_tooltip": "{} en progreso", "history_completed_tooltip": "{} completado", - "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta del modo de recepción: {}", + "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}", "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", - "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.


    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", "receive_mode_received_file": "Recibido: {}", "gui_mode_share_button": "Compartir archivos", @@ -184,6 +184,7 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", - "gui_upload_finished_range": "Cargado {} a {}" + "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", + "gui_upload_finished_range": "Cargado {} a {}", + "timeout_upload_still_running": "Esperando a que se complete la subida" } diff --git a/share/locale/fa.json b/share/locale/fa.json new file mode 100644 index 00000000..9b15a20d --- /dev/null +++ b/share/locale/fa.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "خروج", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "افزودن", + "gui_delete": "Delete", + "gui_choose_items": "گزینش", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "Canceled", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "خروج", + "gui_quit_warning_dont_quit": "لغو", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "تنظیمات", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "هرگز", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "تنظیمات عمومی", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "ذخیره", + "gui_settings_button_cancel": "لغو", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "بله", + "gui_tor_connection_ask_quit": "خروج", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "درحال دریافت", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "فهرست", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "زبان ترجیحی", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/fr.json b/share/locale/fr.json index eecf1481..38580eb1 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -7,15 +7,15 @@ "closing_automatically": "Arrêt automatique car le téléchargement est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare démarré", - "systray_download_started_message": "Un utilisateur télécharge vos fichiers", + "systray_download_started_message": "Une personne télécharge vos fichiers", "systray_download_completed_title": "Téléchargement OnionShare terminé", "systray_download_canceled_title": "Téléchargement OnionShare annulé", - "systray_download_canceled_message": "L'utilisateur a annulé le téléchargement", + "systray_download_canceled_message": "La personne a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", "help_stay_open": "Continuer le partage après le premier téléchargement", "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", - "gui_drag_and_drop": "Glissez déposez\nles fichiers ici", + "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionnez", @@ -37,12 +37,12 @@ "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", "timeout_download_still_running": "En attente de la fin du téléchargement", - "systray_download_completed_message": "L'utilisateur a terminé de télécharger vos fichiers", + "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers", "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", - "gui_settings_connection_type_label": "Comment OnionShare doit se connecter à Tor ?", + "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", "gui_settings_socket_file_label": "Fichier socket", @@ -68,7 +68,7 @@ "gui_share_stop_server_shutdown_timeout": "Arrêter le partage ({}s restantes)", "systray_upload_started_title": "Envoi OnionShare démarré", "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur", - "gui_no_downloads": "Pas encore de téléchargements", + "gui_no_downloads": "Pas encore de téléchargement", "gui_copied_url_title": "Adresse OnionShare copiée", "gui_quit_title": "Pas si vite", "gui_share_quit_warning": "Vous êtes en train d'envoyer des fichiers. Voulez-vous vraiment quitter OnionShare ?", @@ -154,5 +154,33 @@ "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", - "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)" + "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)", + "timeout_upload_still_running": "En attente de la fin de l'envoi", + "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée, vous pouvez maintenant\ncliquer pour copier votre HidServAuth.", + "gui_settings_autoupdate_check_button": "Vérifier les mises à jour", + "settings_test_success": "Connecté au contrôleur Tor.\n\nVersion de Tor : {}\nSupport des services oignon éphémères : {}.\nSupport de l'authentification client : {}.\nSupport de la nouvelle génération d'adresses .onion : {}.", + "update_error_check_error": "Impossible de vérifier les mises à jour : le site web d'OnionShare dit que la dernière version est la méconnaissable '{}'…", + "update_error_invalid_latest_version": "Impossible de vérifier les mises à jour : peut-être qu'il n'y a pas de connexion à Tor ou que le site web d'OnionShare est hors service ?", + "gui_tor_connection_ask": "Ouvrir les paramètres pour configurer la connexion à Tor ?", + "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nVérifiez votre connexion à Internet, puis réouvrez OnionShare et configurez sa connexion à Tor.", + "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses legacy", + "info_in_progress_uploads_tooltip": "{} envoi(s) en cours", + "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)", + "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", + "receive_mode_upload_starting": "Un envoi d'une taille totale de {} a commencé", + "systray_close_server_message": "Une personne a arrêté le serveur", + "systray_page_loaded_title": "Page OnionShare chargée", + "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", + "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_settings_tor_bridges_obfs4_radio_option": "Utiliser les transports enfichables obfs4 intégrés", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (nécessitent obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (nécessitent obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Avertissement : les bridges meek_lite sont très coûteux à faire fonctionner pour le Tor Project.

    Utilisez les seulement si vous ne pouvez pas vous connecter à Tor directement, via les transports obfs4 ou avec d'autres bridges normaux.", + "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", + "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", + "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré" } diff --git a/share/locale/gu.json b/share/locale/gu.json new file mode 100644 index 00000000..8ebfc5fb --- /dev/null +++ b/share/locale/gu.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "ક્યારેય નહીં", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/hu.json b/share/locale/hu.json new file mode 100644 index 00000000..34cc480b --- /dev/null +++ b/share/locale/hu.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Kilépés", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "Delete", + "gui_choose_items": "Kiválaszt", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Kilépés", + "gui_quit_warning_dont_quit": "Megszakítás", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "Beállítások", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Soha", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Általános beállítások", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "Kontroll port", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "Mentés", + "gui_settings_button_cancel": "Megszakítás", + "gui_settings_button_help": "Súgó", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Igen", + "gui_tor_connection_ask_quit": "Kilépés", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "Megosztás", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "Bevétel", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Tallózás", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "Az összes törlése", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "Előnyben részesített nyelv", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/id.json b/share/locale/id.json new file mode 100644 index 00000000..24da50a0 --- /dev/null +++ b/share/locale/id.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Keluar", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "Tambahkan", + "gui_delete": "Hapus", + "gui_choose_items": "Pilih", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "Canceled", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Keluar", + "gui_quit_warning_dont_quit": "Batal", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "Pengaturan", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Tak pernah", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Pengaturan umum", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "Port kontrol", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "Simpan", + "gui_settings_button_cancel": "Batal", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Ya", + "gui_tor_connection_ask_quit": "Keluar", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Jelajahi", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/is.json b/share/locale/is.json new file mode 100644 index 00000000..85b492ab --- /dev/null +++ b/share/locale/is.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Hætta", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Hætta", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Aldrei", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Almennar stillingar", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Já", + "gui_tor_connection_ask_quit": "Hætta", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Flakka", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/it.json b/share/locale/it.json index ebe2df4e..a6f6d265 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,7 +1,7 @@ { "preparing_files": "Preparazione dei files da condividere.", "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", - "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", + "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", "not_a_file": "{0:s} non è un file.", "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica dopo aver finito il download", @@ -16,10 +16,72 @@ "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", - "gui_copy_url": "Copia lo URL", - "gui_downloads": "Downloads:", + "gui_copy_url": "Copia l' URL", + "gui_downloads": "Cronologia Dei Download", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", "gui_please_wait": "Attendere prego...", - "zip_progress_bar_format": "Elaborazione files: %p%" + "zip_progress_bar_format": "Elaborazione files: %p%", + "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", + "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al recipiente:", + "give_this_url_receive": "Dai questo indirizzo al mittente:", + "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth:", + "not_a_readable_file": "{0:s} non e' un file leggibile.", + "no_available_port": "Non trovo nessuna porta disponibile per far partire un onion service", + "close_on_timeout": "tempo scaduto, chiudo", + "timeout_download_still_running": "download in corso, attendere", + "systray_menu_exit": "Fine", + "systray_download_started_title": "Inizio il Download con OnionShare", + "systray_download_started_message": "Una persona ha iniziato il download dei tuoi file", + "systray_download_completed_title": "Download completato", + "systray_download_completed_message": "il download dei tuoi file è stato completato", + "systray_download_canceled_title": "Download cancellato", + "systray_download_canceled_message": "la persona ha interrotto il download", + "systray_upload_started_title": "Iniziando upload", + "systray_upload_started_message": "Iniziando upload di file sul tuo computer", + "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", + "help_stealth": "Richiedi autorizzazione (avanzato)", + "help_config": "Specifica il path del file JSON personalizzato", + "gui_share_stop_server_shutdown_timeout": "Ferma la condivisione ({}s rimanenti)", + "gui_share_stop_server_shutdown_timeout_tooltip": "timer termina in {}", + "gui_receive_start_server": "Inizia modalitá ricezione", + "gui_receive_stop_server": "Termina modalitá ricezione", + "gui_receive_stop_server_shutdown_timeout": "Interrompi modalitá ricezione ({}s rimanenti)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "timer termina in {}", + "gui_copy_hidservauth": "Copia URL segreto", + "gui_no_downloads": "ancora niente", + "gui_copied_url_title": "Indirizzo OnionShare copiato", + "gui_copied_hidservauth_title": "URL segreto copiato", + "gui_copied_hidservauth": "URL segreto copiato", + "gui_download_upload_progress_complete": "%p%, {0:s} mancanti.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calcolando)", + "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org", + "gui_quit_title": "Non cosí in fretta", + "gui_share_quit_warning": "Stai per inviare dai file. Sei sicuro di voler uscire da OnionShare?", + "gui_receive_quit_warning": "Stai ricevendo file, vuoi davvero terminare e chiudere OnionShare?", + "gui_quit_warning_quit": "Esci", + "gui_quit_warning_dont_quit": "Cancella", + "error_rate_limit": "Qualcosa ha fatto troppi tentativi a questo indirizzo, questo potrebbe esporre la sicurezza dell'indirizzo, quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL che verrá generato.", + "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", + "gui_settings_window_title": "Preferenze", + "gui_settings_whats_this": "Cos'e' questo?", + "help_receive": "Ricevi condivisioni invece di inviarle", + "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", + "gui_settings_stealth_hidservauth_string": "Dopo aver salvato la tua chiave privata per il riutilizzo, significa che ora puoi\nclicca per copiare il tuo HidServAuth.", + "gui_settings_autoupdate_label": "controlla la nuova versione", + "gui_settings_autoupdate_option": "Notificami quando una nuova versione è disponibile", + "gui_settings_autoupdate_timestamp": "ultimo controllo", + "gui_settings_autoupdate_timestamp_never": "Mai", + "gui_settings_autoupdate_check_button": "Controlla per una nuova nuova versione", + "gui_settings_general_label": "Impostazioni generali", + "gui_settings_sharing_label": "Impostazione Condivise", + "gui_settings_close_after_first_download_option": "Ferma la condivisione dopo il primo download", + "gui_settings_connection_type_label": "Come OnionShare si connette a tor?", + "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", + "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", + "gui_settings_language_label": "Lingua preferita", + "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", + "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati" } diff --git a/share/locale/ka.json b/share/locale/ka.json new file mode 100644 index 00000000..0f6541fa --- /dev/null +++ b/share/locale/ka.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "პროგრამის დატოვება", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "პროგრამის დატოვება", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "პროგრამის დატოვება", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "არჩევა", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/ko.json b/share/locale/ko.json new file mode 100644 index 00000000..c152c33a --- /dev/null +++ b/share/locale/ko.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "종료", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "삭제", + "gui_choose_items": "선택", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "취소 된", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "종료", + "gui_quit_warning_dont_quit": "취소", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "설정", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "하지 않음", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "일반 설정", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "제어 포트", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "저장", + "gui_settings_button_cancel": "취소", + "gui_settings_button_help": "도움말", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "종료", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "수익", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "보기", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "모두 삭제", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "선호 언어", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/lg.json b/share/locale/lg.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/lg.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/mk.json b/share/locale/mk.json new file mode 100644 index 00000000..2273ba1e --- /dev/null +++ b/share/locale/mk.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Излези", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Излези", + "gui_quit_warning_dont_quit": "Откажи", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "Поставки", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Никогаш", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Општи поставувања", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "Зачувување", + "gui_settings_button_cancel": "Откажи", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "Излези", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Преглед", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/nl.json b/share/locale/nl.json index abd14753..3ecc763b 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -1,72 +1,72 @@ { - "config_onion_service": "Onion service configureren op poort {0:d}.", - "preparing_files": "Bestanden om te delen aan het voorbereiden.", - "give_this_url": "Geef deze URL aan de persoon aan wie je dit bestand verzend:", - "give_this_url_stealth": "Geef deze URL en de HidServAuth regel aan de persoon aan wie je dit bestand verzend:", - "ctrlc_to_stop": "Druk Ctrl-C om de server te stoppen", - "not_a_file": "{0:s} is geen bestand.", + "config_onion_service": "Onion-dienst configureren op poort {0:d}.", + "preparing_files": "Bezig met comprimeren van bestanden.", + "give_this_url": "Geef dit adres aan de ontvanger:", + "give_this_url_stealth": "Geef dit adres en de HidServAuth-regel aan de ontvanger:", + "ctrlc_to_stop": "Druk op Ctrl+C om de server te stoppen", + "not_a_file": "{0:s} is geen geldig bestand.", "not_a_readable_file": "{0:s} is geen leesbaar bestand.", - "no_available_port": "Kan de Onion service niet starten, er zijn geen poorten beschikbaar.", - "other_page_loaded": "URL geladen", - "close_on_timeout": "Sluit automatisch omdat timeout bereikt is", - "closing_automatically": "Sluit automatisch omdat download gereed is", - "timeout_download_still_running": "Wachten totdat download gereed is voor automatisch sluiten", - "large_filesize": "Waarschuwing: Versturen van grote bestanden kan uren duren", + "no_available_port": "Er is geen poort beschikbaar om de onion-dienst op te starten", + "other_page_loaded": "Adres geladen", + "close_on_timeout": "Gestopt omdat de automatische time-out bereikt is", + "closing_automatically": "Gestopt omdat de download is afgerond", + "timeout_download_still_running": "Bezig met wachten op afronden van download", + "large_filesize": "Waarschuwing: het versturen van grote bestanden kan uren duren", "systray_menu_exit": "Afsluiten", - "systray_download_started_title": "OnionShare download gestart", + "systray_download_started_title": "OnionShare-download gestart", "systray_download_started_message": "Een gebruiker is begonnen met downloaden van je bestanden", - "systray_download_completed_title": "OnionShare download gereed", + "systray_download_completed_title": "OnionShare-download afgerond", "systray_download_completed_message": "De gebruiker is klaar met downloaden", - "systray_download_canceled_title": "OnionShare download afgebroken", + "systray_download_canceled_title": "OnionShare-download afgebroken", "systray_download_canceled_message": "De gebruiker heeft de download afgebroken", - "help_local_only": "Maak geen gebruik van Tor, alleen voor ontwikkeling", - "help_stay_open": "Laat verborgen service draaien nadat download gereed is", - "help_shutdown_timeout": "Sluit de Onion service na N seconden", - "help_stealth": "Maak stealth Onion service (geavanceerd)", - "help_debug": "Log fouten naar harde schijf", + "help_local_only": "Tor niet gebruiken (alleen voor ontwikkelingsdoeleinden)", + "help_stay_open": "Blijven delen na afronden van eerste download", + "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", + "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", - "help_config": "Pad naar een JSON configuratie bestand (optioneel)", + "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", "gui_add": "Toevoegen", "gui_delete": "Verwijder", "gui_choose_items": "Kies", "gui_copy_url": "Kopieer URL", "gui_copy_hidservauth": "Kopieer HidServAuth", - "gui_downloads": "Downloads:", + "gui_downloads": "Download Geschiedenis", "gui_canceled": "Afgebroken", - "gui_copied_url": "URL gekopieerd naar klembord", + "gui_copied_url": "OnionShare adres gekopieerd naar klembord", "gui_copied_hidservauth": "HidServAuth regel gekopieerd naar klembord", - "gui_please_wait": "Moment geduld...", - "gui_download_upload_progress_complete": "%p%, Tijd verstreken: {0:s}", - "gui_download_upload_progress_starting": "{0:s}, %p% (ETA berekenen)", + "gui_please_wait": "Aan het starten... Klik om te annuleren.", + "gui_download_upload_progress_complete": "%p%, {0:s} verstreken.", + "gui_download_upload_progress_starting": "{0:s}, %p% (berekenen)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", - "version_string": "Onionshare {0:s} | https://onionshare.org/", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", "gui_quit_warning_quit": "Afsluiten", "gui_quit_warning_dont_quit": "Niet afsluiten", - "error_rate_limit": "Een aanvaller probeert misschien je URL te gokken. Om dit te voorkomen heeft OnionShare de server automatisch gestopt. Om de bestanden te delen moet je de server opnieuw starten en de nieuwe URL delen.", - "zip_progress_bar_format": "Bestanden verwerken: %p%", - "error_stealth_not_supported": "Om een geheime onion service te maken heb je minstens Tor 0.2.9.1-alpha (of Tor Browser 6.5) en minstens python3-stem 1.5.0 nodig.", - "error_ephemeral_not_supported": "OnionShare vereist minstens Tor 0.2.7.1 en minstens python3-stem 1.4.0.", + "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", + "zip_progress_bar_format": "Comprimeren: %p%", + "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.", + "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", - "gui_settings_stealth_option": "Maak stealth onion services", - "gui_settings_autoupdate_label": "Controleer voor updates", - "gui_settings_autoupdate_option": "Notificeer me als er updates beschikbaar zijn", + "gui_settings_stealth_option": "Gebruik client authorization (achterhaald)", + "gui_settings_autoupdate_label": "Controleer op updates", + "gui_settings_autoupdate_option": "Laat me weten als er een nieuwe versie beschikbaar is", "gui_settings_autoupdate_timestamp": "Laatste controle: {}", "gui_settings_autoupdate_timestamp_never": "Nooit", - "gui_settings_autoupdate_check_button": "Controleer voor update", - "gui_settings_sharing_label": "Deel opties", + "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie", + "gui_settings_sharing_label": "Instelling voor delen", "gui_settings_close_after_first_download_option": "Stop delen na eerste download", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", - "gui_settings_connection_type_bundled_option": "Gebruik Tor die is meegeleverd met OnionShare", - "gui_settings_connection_type_automatic_option": "Probeer automatische configuratie met Tor Browser", + "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare", + "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser", "gui_settings_connection_type_control_port_option": "Verbinden via controle poort", "gui_settings_connection_type_socket_file_option": "Verbinden via socket bestand", - "gui_settings_connection_type_test_button": "Test Tor instellingen", + "gui_settings_connection_type_test_button": "Test Connectie naar Tor", "gui_settings_control_port_label": "Controle poort", "gui_settings_socket_file_label": "Socket bestand", "gui_settings_socks_label": "SOCKS poort", - "gui_settings_authenticate_label": "Tor authenticatie opties", + "gui_settings_authenticate_label": "Tor authenticatie instellingen", "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", "gui_settings_password_label": "Wachtwoord", @@ -75,29 +75,112 @@ "gui_settings_button_help": "Help", "gui_settings_shutdown_timeout": "Stop delen om:", "settings_saved": "Instellingen opgeslagen in {}", - "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat de instellingen niet kloppen.", - "settings_error_automatic": "Kan geen verbinding maken met de Tor controller. Draait Tor Browser in de achtergrond? Deze kan je verkrijgen via https://www.torproject.org/.", + "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat je instellingen nergens op slaan.", + "settings_error_automatic": "Kon geen verbinding maken met de Tor controller. Draait Tor Browser (beschikbaar via torproject.org) in de achtergrond?", "settings_error_socket_port": "Kan geen verbinding maken met de Tor controller op {}:{}.", "settings_error_socket_file": "Kan geen verbinding maken met de Tor controller via socket bestand {}.", "settings_error_auth": "Verbonden met {}:{}, maar kan niet authenticeren. Misschien is het geen Tor controller?", "settings_error_missing_password": "Verbonden met Tor controller, maar het heeft een wachtwoord nodig voor authenticatie.", - "settings_error_unreadable_cookie_file": "Verbonden met Tor controller, maar kan niet authenticeren omdat wachtwoord onjuist is en gebruiker heeft niet de permissies om cookie bestand te lezen.", - "settings_error_bundled_tor_not_supported": "Meegeleverde Tor is niet onersteunt wanneer je niet de ontwikkelaarsmodus gebruikt in Windows or macOS.", - "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer offline, of je klok is niet accuraat.", + "settings_error_unreadable_cookie_file": "Verbonden met de Tor controller, maar het wachtwoord kan onjuist zijn, of je gebruiker heeft geen toestemming om het cookie bestand te lezen.", + "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de developer mode op Windows of macOS.", + "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", - "settings_test_success": "Gefeliciteerd, OnionShare kan verbinden met de Tor controller.\n\nTor version: {}\nOndersteunt kortstondige onion services: {}\nOndersteunt geheime onion services: {}", - "error_tor_protocol_error": "Fout bij praten met de Tor controller.\nAls je Whonix gebruikt, kijk dan hier https://www.whonix.org/wiki/onionshare om OnionShare werkend te krijgen.", + "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", + "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", - "update_available": "Er is een OnionShare update beschikbaar. Klik hier om te downloaden.

    Geinstalleerde versie: {}
    Laatste versie: {}", - "update_error_check_error": "Fout bij controleren voor updates: Misschien ben je niet verbonden met Tor, of misschien is de OnionShare website down.", - "update_error_invalid_latest_version": "Fout bij controleren voor updates: De OnionShare website zegt dat de laatste versie '{}' is, maar dat lijkt geen valide versie te zijn.", - "update_not_available": "Je draait de laatste versie van OnionShare.", - "gui_tor_connection_ask": "Wil je de OnionShare instellingen openen om het verbindingsprobleem met Tor op te lossen?", - "gui_tor_connection_ask_open_settings": "Open Instellingen", + "update_available": "Er is een nieuwe versie van OnionShare beschikbaar. Klik hier om te updaten.

    Je hebt nu {} en de laatste versie is {}.", + "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", + "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of is de OnionShare website is niet beschikbaar?", + "update_not_available": "Je draait de laatst beschikbare OnionShare.", + "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", + "gui_tor_connection_ask_open_settings": "Ja", "gui_tor_connection_ask_quit": "Afsluiten", - "gui_tor_connection_error_settings": "Probeer de instellingen hoe OnionShare verbind met het Tor network aan te passen in Instellingen.", - "gui_tor_connection_canceled": "OnionShare kan niet verbinden met Tor.\n\nControleer of je verbonden bent met het internet, herstart OnionShare om de Tor verbinding te configureren.", - "gui_server_started_after_timeout": "De server startte na de gekozen auto-timeout.\nDeel opnieuw.", - "gui_server_timeout_expired": "De gekozen timeout is al verlopen.\nKies nieuwe timeout deel opnieuw.", - "share_via_onionshare": "Deel via OnionShare" + "gui_tor_connection_error_settings": "Probeer hoe OnionShare verbind met het Tor network te veranderen in de instellingen.", + "gui_tor_connection_canceled": "Kon niet verbinden met Tor.\n\nWees er zeker van dat je verbonden bent met het internet, herstart OnionShare en configureer de verbinding met Tor.", + "gui_server_started_after_timeout": "De auto-stop timer liep af voordat de server startte.\nMaak een nieuwe share aan.", + "gui_server_timeout_expired": "De auto-stop timer is al verlopen.\nStel een nieuwe tijd in om te beginnen met delen.", + "share_via_onionshare": "Deel via OnionShare", + "give_this_url_receive": "Geef dit adres aan de afzender:", + "give_this_url_receive_stealth": "Geef dit adres en de HidServAuth-regel aan de afzender:", + "systray_upload_started_title": "OnionShare-upload gestart", + "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer", + "help_receive": "Bestanden ontvangen in plaats van ze versturen", + "timeout_upload_still_running": "Wachten op voltooiing van de upload", + "gui_share_start_server": "Start delen", + "gui_share_stop_server": "Stop delen", + "gui_share_stop_server_shutdown_timeout": "Stop Delen ({}s resterend)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer eindigt bij {}", + "gui_receive_start_server": "Start Ontvangst Mode", + "gui_receive_stop_server": "Stop Ontvangst Mode", + "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangst Mode ({}s resterend)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer stopt bij {}", + "gui_no_downloads": "Nog Geen Downloads", + "gui_copied_url_title": "Gekopieerd OnionShare Adres", + "gui_copied_hidservauth_title": "HidServAuth gekopieerd", + "gui_quit_title": "Niet zo snel", + "gui_receive_quit_warning": "Je ben bezig files te ontvangen. Weet je zeker dat je OnionShare wilt stoppen?", + "gui_settings_whats_this": "1What is dit?2", + "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je klikken om je HidServAuth te kopiëren.", + "gui_settings_general_label": "Algemene instellingen", + "gui_settings_tor_bridges": "Tor bridge ondersteuning", + "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", + "gui_settings_tor_bridges_obfs4_radio_option": "Gebruik ingebouwde obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Gebruik ingebouwde pluggable transports (vereist obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Gebruik ingebouwde meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Gebruik ingebouwde meek_lite (Azure) pluggable transports (vereist obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Waarschuwing: De meek_lite bridges zijn erg kostbaar voor het Tor Project om uit te voeren.

    Gebruik ze alleen als je niet direct met Tor kan verbinden, via obfs4 transports, of andere normale bridges.", + "gui_settings_tor_bridges_custom_radio_option": "Gebruik custom bridges", + "gui_settings_tor_bridges_custom_label": "Je kan bridges krijgen via 1https://bridges.torproject.org2", + "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. Controleer ze of voeg andere toe.", + "gui_settings_shutdown_timeout_checkbox": "Gebruik auto-stop timer", + "error_tor_protocol_error_unknown": "Er was een onbekende fout met Tor", + "error_invalid_private_key": "Dit type privésleutel wordt niet ondersteund", + "gui_tor_connection_lost": "De verbinding met Tor is verbroken.", + "gui_use_legacy_v2_onions_checkbox": "Gebruik ouderwetse adressen", + "gui_save_private_key_checkbox": "Gebruik een blijvend adres (achterhaald)", + "gui_share_url_description": "1Iedereen2 met dit OnionShare adres kan je bestanden 3binnenhalen4 met de 5Tor Browser6: ", + "gui_receive_url_description": "1Iedereen2 met dit OnionShare adres kan bestanden op je computer 3plaatsen4 met de 5Tor Browser6: 7", + "gui_url_label_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", + "gui_url_label_stay_open": "Deze share stopt niet automatisch.", + "gui_url_label_onetime": "Deze share stopt na de eerste voltooiing.", + "gui_url_label_onetime_and_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", + "gui_status_indicator_share_stopped": "Klaar om te delen", + "gui_status_indicator_share_working": "Starten…", + "gui_status_indicator_share_started": "Aan het delen", + "gui_status_indicator_receive_stopped": "Klaar om te ontvangen", + "gui_status_indicator_receive_working": "Starten…", + "gui_status_indicator_receive_started": "Ontvangen", + "gui_file_info": "{} bestanden, {}", + "gui_file_info_single": "{} bestand, {}", + "history_in_progress_tooltip": "{} bezig", + "history_completed_tooltip": "{} klaar", + "info_in_progress_uploads_tooltip": "{} upload(s) zijn bezig", + "info_completed_uploads_tooltip": "de {} upload(s) zijn klaar", + "error_cannot_create_downloads_dir": "Kon de ontvangst mode map niet maken: {}", + "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}", + "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", + "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten.

    Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", + "receive_mode_upload_starting": "Upload met totale grootte {} is aan het starten", + "receive_mode_received_file": "Ontvangen: {}", + "gui_mode_share_button": "Deel Bestanden", + "gui_mode_receive_button": "Ontvang Bestanden", + "gui_settings_receiving_label": "Instellingen voor Ontvangen", + "gui_settings_downloads_label": "Sla bestanden op naar", + "gui_settings_downloads_button": "Surf", + "gui_settings_public_mode_checkbox": "Publieke mode", + "systray_close_server_title": "OnionShare Server Afgesloten", + "systray_close_server_message": "Een gebruiker heeft de server gestopt", + "systray_page_loaded_title": "OnionShare Pagina Geladen", + "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen", + "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen", + "gui_uploads": "Upload geschiedenis", + "gui_no_uploads": "Er zijn nog geen uploads", + "gui_clear_history": "Wis alles", + "gui_upload_in_progress": "Upload is gestart{}", + "gui_upload_finished_range": "{} is naar {} gestuurd", + "gui_upload_finished": "Verzonden {}", + "gui_download_in_progress": "Binnenhalen gestart {}", + "gui_open_folder_error_nautilus": "Kan de map niet openen omdat bestandsbeheerprogramma nautilus niet beschikbaar is. Het bestand staat hier : {}", + "gui_settings_language_label": "Voorkeurstaal", + "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd." } diff --git a/share/locale/pa.json b/share/locale/pa.json new file mode 100644 index 00000000..a2a967cc --- /dev/null +++ b/share/locale/pa.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "ਬਾਹਰ", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "ਬਾਹਰ", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "ਜਨਰਲ ਸੈਟਿੰਗਜ਼", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "ਬਾਹਰ", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/pl.json b/share/locale/pl.json new file mode 100644 index 00000000..c45af162 --- /dev/null +++ b/share/locale/pl.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Wyjście", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "Usuń", + "gui_choose_items": "Wybierz", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "Anulowano", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Wyjście", + "gui_quit_warning_dont_quit": "Anuluj", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "Ustawienia", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "Sprawdź nową wersję", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Nigdy", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Ustawienia ogólne", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "Port sterowania", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Hasło", + "gui_settings_password_label": "Hasło", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "Zapisz", + "gui_settings_button_cancel": "Anuluj", + "gui_settings_button_help": "Pomoc", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Tak", + "gui_tor_connection_ask_quit": "Wyjście", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "Udostępnianie", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "Otrzymuję", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Wybierz...", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "Wyczyść wszystko", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "Preferowany język", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index abe7c795..f0c839ef 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -164,7 +164,7 @@ "gui_mode_receive_button": "Receber Arquivos", "gui_settings_receiving_label": "Configurações de recepção", "gui_settings_downloads_label": "Armazenar arquivos em", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Navegar", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare encerrado", @@ -177,7 +177,7 @@ "gui_clear_history": "Limpar Tudo", "gui_upload_in_progress": "Upload Iniciado {}", "gui_upload_finished_range": "Upload de {} feito para {}", - "gui_upload_finished": "", + "gui_upload_finished": "Upload realizado de {}", "gui_download_in_progress": "Download Iniciado {}", "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", "gui_settings_language_label": "Idioma preferido", diff --git a/share/locale/pt_PT.json b/share/locale/pt_PT.json index a67a5f75..7085f2c2 100644 --- a/share/locale/pt_PT.json +++ b/share/locale/pt_PT.json @@ -1,20 +1,20 @@ { "config_onion_service": "", "preparing_files": "", - "give_this_url": "", + "give_this_url": "Passe este URL para a pessoa que deve receber o arquivo:", "give_this_url_stealth": "", "give_this_url_receive": "", "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", + "ctrlc_to_stop": "Pressione Ctrl-C para parar o servidor", + "not_a_file": "{0:s} não é um arquivo.", "not_a_readable_file": "", "no_available_port": "", - "other_page_loaded": "", + "other_page_loaded": "Outra página tem sido carregada", "close_on_timeout": "", "closing_automatically": "", "timeout_download_still_running": "", "large_filesize": "", - "systray_menu_exit": "", + "systray_menu_exit": "Sair", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", @@ -33,8 +33,8 @@ "help_config": "", "gui_drag_and_drop": "", "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", + "gui_delete": "Eliminar", + "gui_choose_items": "Escolha", "gui_share_start_server": "", "gui_share_stop_server": "", "gui_share_stop_server_shutdown_timeout": "", @@ -47,9 +47,9 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", + "gui_canceled": "Cancelada", "gui_copied_url_title": "", - "gui_copied_url": "", + "gui_copied_url": "URL foi copiado para área de transferência", "gui_copied_hidservauth_title": "", "gui_copied_hidservauth": "", "gui_please_wait": "", @@ -60,22 +60,22 @@ "gui_quit_title": "", "gui_share_quit_warning": "", "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "Sair", + "gui_quit_warning_dont_quit": "Cancelar", "error_rate_limit": "", "zip_progress_bar_format": "", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", + "gui_settings_window_title": "Definições", "gui_settings_whats_this": "", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "", "gui_settings_autoupdate_option": "", "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", + "gui_settings_general_label": "Definições gerais", "gui_settings_sharing_label": "", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", @@ -89,8 +89,8 @@ "gui_settings_socks_label": "", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", "gui_settings_tor_bridges": "", "gui_settings_tor_bridges_no_bridges_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option": "", @@ -102,8 +102,8 @@ "gui_settings_tor_bridges_custom_label": "", "gui_settings_tor_bridges_invalid": "", "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", + "gui_settings_button_cancel": "Cancelar", + "gui_settings_button_help": "Ajuda", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", "settings_error_unknown": "", @@ -126,8 +126,8 @@ "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", + "gui_tor_connection_ask_open_settings": "Sim", + "gui_tor_connection_ask_quit": "Sair", "gui_tor_connection_error_settings": "", "gui_tor_connection_canceled": "", "gui_tor_connection_lost": "", @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", + "gui_settings_downloads_button": "Navegar", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", diff --git a/share/locale/ro.json b/share/locale/ro.json new file mode 100644 index 00000000..b46f0b5d --- /dev/null +++ b/share/locale/ro.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Închidere", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "Şterge", + "gui_choose_items": "Alegeți", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "Canceled", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Închidere", + "gui_quit_warning_dont_quit": "Anulare", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "Setari", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Niciodata", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Setări generale", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "Port de control", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Parolă", + "gui_settings_password_label": "Parolă", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "Salvare", + "gui_settings_button_cancel": "Anulare", + "gui_settings_button_help": "Ajutor", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Da", + "gui_tor_connection_ask_quit": "Închidere", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "Impartasirea creatiilor", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "Primire", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Răsfoiește", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/ru.json b/share/locale/ru.json index b7c89d69..254b5ef8 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,8 +1,185 @@ { - "give_this_url": "Отправьте эту ссылку тому человеку, которому вы хотите передать файл:", - "ctrlc_to_stop": "Нажмите Ctrl-C чтобы остановить сервер", - "not_a_file": "{0:s} не является файлом.", - "gui_copied_url": "Ссылка скопирована в буфер обмена", - "other_page_loaded": "Другая страница была загружена", - "gui_copy_url": "Скопировать ссылку" + "give_this_url": "Передайте этот адрес получателю:", + "ctrlc_to_stop": "Нажмите Ctrl+C, чтобы остановить сервер", + "not_a_file": "{0:s} недопустимый файл.", + "gui_copied_url": "Ссылка OnionShare скопирована в буфер обмена", + "other_page_loaded": "Адрес загружен", + "gui_copy_url": "Скопировать ссылку", + "systray_menu_exit": "Выйти", + "gui_add": "Добавить", + "gui_delete": "Удалить", + "gui_choose_items": "Выбрать", + "gui_canceled": "Отменена", + "gui_quit_warning_quit": "Выйти", + "gui_quit_warning_dont_quit": "Отмена", + "gui_settings_window_title": "Настройки", + "gui_settings_autoupdate_timestamp_never": "Никогда", + "gui_settings_general_label": "Общие настройки", + "gui_settings_control_port_label": "Контрольный порт", + "gui_settings_authenticate_password_option": "Пароль", + "gui_settings_password_label": "Пароль", + "gui_settings_button_save": "Сохранить", + "gui_settings_button_cancel": "Отмена", + "gui_settings_button_help": "Помощь", + "gui_tor_connection_ask_open_settings": "Есть", + "gui_tor_connection_ask_quit": "Выйти", + "gui_status_indicator_share_started": "Идёт раздача", + "gui_status_indicator_receive_started": "Идёт получение", + "gui_settings_downloads_label": "Сохранять файлы в", + "gui_settings_downloads_button": "Выбрать", + "gui_clear_history": "Очистить Все", + "gui_settings_language_label": "Предпочтительный язык", + "config_onion_service": "Назначем \"луковому\" сервису порт {:d}.", + "preparing_files": "Сжимаем файлы.", + "give_this_url_stealth": "Передайте этот адрес и строку HidServAuth получателю:", + "give_this_url_receive": "Передайте этот адрес отправителю:", + "give_this_url_receive_stealth": "Передайте этот адрес и строку HidServAuth отправителю:", + "not_a_readable_file": "{0:s} не читаемый файл.", + "no_available_port": "Не удалось найти доступный порт для запуска \"лукового\" сервиса", + "close_on_timeout": "Время ожидания таймера истекло, сервис остановлен", + "closing_automatically": "Загрузка завершена, сервис остановлен", + "timeout_download_still_running": "Ожидаем завершения загрузки", + "timeout_upload_still_running": "Ожидаем завершения выгрузки", + "large_filesize": "Внимание: Отправка раздачи большого объёма может занять продолжительное время (часы)", + "systray_download_started_title": "OnionShare: Загрузка Началась", + "systray_download_started_message": "Пользователь начал загружать ваши файлы", + "systray_download_completed_title": "OnionShare: Загрузка Завершена", + "systray_download_completed_message": "Пользователь завершил загрузку ваших файлов", + "systray_download_canceled_title": "OnionShare: Загрузка Отменена", + "systray_download_canceled_message": "Пользователь отменил загрузку", + "systray_upload_started_title": "OnionShare: Выгрузка Началась", + "systray_upload_started_message": "Пользователь начал выгрузку файлов на ваш компьютер", + "help_local_only": "Не использовать Tor (только для разработки)", + "help_stay_open": "Продолжить раздачу после первой загрузки", + "help_shutdown_timeout": "Остановить раздачу после заданного количества секунд", + "help_stealth": "Использовать авторизацию клиента (дополнительно)", + "help_receive": "Получать раздачи, вместо их отправки", + "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск", + "help_filename": "Список файлов или папок для раздачи", + "help_config": "Расположение пользовательского конфигурационного JSON-файла (необязательно)", + "gui_drag_and_drop": "Перетащите сюда файлы и папки\nчтобы начать раздачу", + "gui_share_start_server": "Начать раздачу", + "gui_share_stop_server": "Закончить раздачу", + "gui_share_stop_server_shutdown_timeout": "Остановить раздачу ({}s осталось)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", + "gui_receive_start_server": "Включить Режим Получения", + "gui_receive_stop_server": "Выключить Режим Получения", + "gui_receive_stop_server_shutdown_timeout": "Выключить Режим Получения ({}s осталось)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", + "gui_copy_hidservauth": "Скопировать строку HidServAuth", + "gui_downloads": "История Загрузок", + "gui_no_downloads": "Пока нет загрузок", + "gui_copied_url_title": "Адрес OnionShare скопирован", + "gui_copied_hidservauth_title": "Строка HidServAuth скопирована", + "gui_copied_hidservauth": "Строка HidServAuth скопирована в буфер обмена", + "gui_please_wait": "Начинаем... нажмите, чтобы отменить.", + "gui_download_upload_progress_complete": "%p%, прошло {0:s}.", + "gui_download_upload_progress_starting": "{0:s}, %p% (вычисляем)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Не так быстро", + "gui_share_quit_warning": "Идёт процесс отправки файлов. Вы уверены, что хотите завершить работу OnionShare?", + "gui_receive_quit_warning": "Идёт процесс получения файлов. Вы уверены, что хотите завершить работу OnionShare?", + "error_rate_limit": "Кто-то совершил слишком много неверных попыток подключения к вашему адресу, это может означать, что его пытаются вычислить. OnionShare остановил сервер. Создайте раздачу повторно и перешлите получателю новый адрес.", + "zip_progress_bar_format": "Сжатие: %p%", + "error_stealth_not_supported": "Для использования авторизации клиента необходимы как минимум версии Tor 0.2.9.1-alpha (или Tor Browser 6.5) и библиотеки python3-stem 1.5.0.", + "error_ephemeral_not_supported": "Для работы OnionShare необходимы как минимум версии Tor 0.2.7.1 и библиотеки python3-stem 1.4.0.", + "gui_settings_whats_this": "Что это?", + "gui_settings_stealth_option": "Использовать авторизацию клиента (устарело)", + "gui_settings_stealth_hidservauth_string": "Сохранили ваш приватный ключ для повторного использования,\nзначит теперь вы можете нажать сюда, чтобы скопировать вашу строку HidServAuth.", + "gui_settings_autoupdate_label": "Проверить новую версию", + "gui_settings_autoupdate_option": "Уведомить меня, когда будет доступна новая версия", + "gui_settings_autoupdate_timestamp": "Последняя проверка: {}", + "gui_settings_autoupdate_check_button": "Проверить Новую Версию", + "gui_settings_sharing_label": "Настройки раздачи", + "gui_settings_close_after_first_download_option": "Остановить раздачу после первой загрузки", + "gui_settings_connection_type_label": "Как OnionShare следует подключиться к сети Tor?", + "gui_settings_connection_type_bundled_option": "Использовать версию Tor, встроенную в OnionShare", + "gui_settings_connection_type_automatic_option": "Попробовать автоматическую настройку с Tor Browser", + "gui_settings_connection_type_control_port_option": "Подключиться используя порт управления", + "gui_settings_connection_type_socket_file_option": "Подключиться используя файл-сокет", + "gui_settings_connection_type_test_button": "Проверить подключение к сети Tor", + "gui_settings_socket_file_label": "Файл-сокет", + "gui_settings_socks_label": "Порт SOCKS", + "gui_settings_authenticate_label": "Настройки аутентификации Tor", + "gui_settings_authenticate_no_auth_option": "Без аутентификации или cookie-аутентификации", + "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Не использовать \"мосты\"", + "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные obfs4 подключаемые транспорты", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные obfs4 подключаемые транспорты (необходим obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные meek_lite (Azure) встроенные транспорты", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные meek_lite (Azure) встроенные транспорты (необходим obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project

    Используйте их только если Вы не можете поделючться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".", + "gui_settings_tor_bridges_custom_radio_option": "Использовать пользовательские \"мосты\"", + "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работет.\nПроверьте их снова или добавьте другие.", + "gui_settings_shutdown_timeout_checkbox": "Использовать таймер", + "gui_settings_shutdown_timeout": "Остановить раздачу в:", + "settings_error_unknown": "Невозможно подключить к контроллеру Tor, поскольку ваши настройки не корректны.", + "settings_error_automatic": "Невозможно подключться к контроллеру Tor. Уточните, Tor Browser (можно загрузить по ссылке torproject.org) запущен в фоновом режиме?", + "settings_error_socket_port": "Невозможно подключиться к контроллеру Tor в {}:{}.", + "settings_error_socket_file": "Невозможно подключиться к контроллеру Tor используя файл-сокет {}.", + "settings_error_auth": "Произведено подлючение к {}:{}, не получается проверить подлинность. Возможно, это не контроллер сети Tor?", + "settings_error_missing_password": "Произведено подключение к контроллеру Tor, но для аутентификации необходим пароль.", + "settings_error_unreadable_cookie_file": "Произведено подключение к контроллеру Tor, но пароль может быть указан неверно или пользователю не разрешено чтение файла-cookie.", + "settings_error_bundled_tor_not_supported": "Версию Tor, которая поставляется вместе с OnionShare нельзя использовать в режиме разработки на операционных системах Windows или macOS.", + "settings_error_bundled_tor_timeout": "Подключение к сети Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет или у вас неточно настроено системное время?", + "settings_error_bundled_tor_broken": "OnionShare не смог подулючиться к сети Tor в фоновом режиме:\n{}", + "settings_test_success": "Произведено подключение к контроллеру Tor.\n\nВерсия Tor: {}\nПоддержка временных \"луковых\" сервисов: {}.\nПоддержка аутентификации клиента: {}.\nПоддержка адресов .onion следующего поколения: {}.", + "error_tor_protocol_error": "Произошла ошибка с сетью Tor: {}", + "error_tor_protocol_error_unknown": "Произошла неизвестная ошибка с сетю Tor", + "error_invalid_private_key": "Данный тип приватного ключа не поддерживается", + "connecting_to_tor": "Подключаемся к сети Tor", + "update_available": "Вышла новая версия OnionShare. Нажмите сюда чтобы загрузить.

    Вы используется версию {}, наиболее поздняя версия {}.", + "update_error_check_error": "Не удалось проверить новые версии: сайт OnionShare сообщает, что не удалось распознать наиболее позднюю версию '{}'…", + "update_error_invalid_latest_version": "Не удалось проверить наличие новой версии: возможно вы не подключены к сети Tor или сайт OnionShare не работает?", + "update_not_available": "Вы используете наиболее позднюю версию OnionShare.", + "gui_tor_connection_ask": "Открыть раздел \"настройки\" для решения проблем с подключением к сети Tor?", + "gui_tor_connection_error_settings": "Попробуйте изменить способ, при помощий которого OnionShare подключается к сети Tor в разделе \"Настройки\".", + "gui_tor_connection_canceled": "Не удалось подключиться к сети Tor.\n\nПожалуйста, убедитесь что есть подключение к сети Интернет, затем переоткройте OnionShare и настройте подключение к сети Tor.", + "gui_tor_connection_lost": "Произведено отключение от сети Tor.", + "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, создайте новую раздачу.", + "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для того, чтобы начать раздачу.", + "share_via_onionshare": "OnionShare это", + "gui_use_legacy_v2_onions_checkbox": "Используйте устаревшие адреса", + "gui_save_private_key_checkbox": "Используйте постоянный адрес (устарело)", + "gui_share_url_description": "Кто угодно c этим адресом OnionShare может загрузить ваши файлы при помощиTor Browser: ", + "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может выгрузить файлы на ваш комьютер Tor Browser: ", + "gui_url_label_persistent": "Данная раздаче не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_url_label_stay_open": "Данная раздача не будет остановлена автоматически.", + "gui_url_label_onetime": "Данная раздача будет завершена автоматически после первой загрузки.", + "gui_url_label_onetime_and_persistent": "Данная раздача не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_status_indicator_share_stopped": "Можно начинать раздачу", + "gui_status_indicator_share_working": "Начинаем…", + "gui_status_indicator_receive_stopped": "Можно начинать получение", + "gui_status_indicator_receive_working": "Начинаем…", + "gui_file_info": "{} файлы, {}", + "gui_file_info_single": "{} файл, {}", + "history_in_progress_tooltip": "{} в ходе выполнения", + "history_completed_tooltip": "{} завершено", + "info_in_progress_uploads_tooltip": "{} выгрузка(и) в ходе выполнения", + "info_completed_uploads_tooltip": "{} выгрузка(и) завершена(ы)", + "error_cannot_create_downloads_dir": "Не удалось создать папку в режиме получения: {}", + "receive_mode_downloads_dir": "Файлы, которые были вам отправлены находятся в папке: {}", + "receive_mode_warning": "Внимание: Режим получения позаоляет другия людями загружать файлы на ваш компьютер. Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", + "gui_receive_mode_warning": "Режим получения позаоляет другия людями загружать файлы на ваш компьютер.

    Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", + "receive_mode_upload_starting": "Начинается выгрузка общим объёмом {}", + "receive_mode_received_file": "Получено: {}", + "gui_mode_share_button": "Раздача файлов", + "gui_mode_receive_button": "Получение файлов", + "gui_settings_receiving_label": "Настройки получения", + "gui_settings_public_mode_checkbox": "Публичный режим", + "systray_close_server_title": "Сервер OnionShare Отключен", + "systray_close_server_message": "Пользователь отключил сервер", + "systray_page_loaded_title": "Страница OnionShare Загружена", + "systray_download_page_loaded_message": "Пользователь находится на странице загрузки", + "systray_upload_page_loaded_message": "Пользователь посетил странцу выгрузки", + "gui_uploads": "История Выгрузок", + "gui_no_uploads": "Пока Нет Выгрузок", + "gui_upload_in_progress": "Выгрузка Началась {}", + "gui_upload_finished_range": "Загружено {} в {}", + "gui_upload_finished": "Выгружено {}", + "gui_download_in_progress": "Загрузка Началась {}", + "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку nautilus не доступен. Файл находится здесь: {}", + "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки." } diff --git a/share/locale/sl.json b/share/locale/sl.json new file mode 100644 index 00000000..42892ef9 --- /dev/null +++ b/share/locale/sl.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "Izhod", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "Izberi", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "Odpovedan", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "Izhod", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "Nikoli", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "Splošne nastavitve", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "Krmilna vrata", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "Pomoč", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "Da", + "gui_tor_connection_ask_quit": "Izhod", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "Brskanje", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/sv.json b/share/locale/sv.json new file mode 100644 index 00000000..c5f6e8da --- /dev/null +++ b/share/locale/sv.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "Förbereder onion-tjänsten på port {0:d}.", + "preparing_files": "Komprimera filer.", + "give_this_url": "Ge den här adressen till mottagaren:", + "give_this_url_stealth": "Ge den här adressen och HidServAuth-raden till mottagaren:", + "give_this_url_receive": "Ge den här adressen till avsändaren:", + "give_this_url_receive_stealth": "Ge den här adressen och HidServAuth-raden till avsändaren:", + "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern", + "not_a_file": "{0:s} är inte en giltig fil.", + "not_a_readable_file": "{0:s} är inte en läsbar fil.", + "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", + "other_page_loaded": "Adress laddad", + "close_on_timeout": "", + "closing_automatically": "Stannade för att nedladdningen blev klar", + "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", + "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", + "large_filesize": "Varning: Att skicka en stor fil kan ta timmar", + "systray_menu_exit": "Avsluta", + "systray_download_started_title": "OnionShare Nedladdning Startad", + "systray_download_started_message": "En användare började ladda ner dina filer", + "systray_download_completed_title": "OnionShare Nedladdning Klar", + "systray_download_completed_message": "Användaren har laddat ner dina filer", + "systray_download_canceled_title": "OnionShare Nedladdning Avbruten", + "systray_download_canceled_message": "Användaren avbröt nedladdningen", + "systray_upload_started_title": "OnionShare Uppladdning Påbörjad", + "systray_upload_started_message": "En användare började ladda upp filer på din dator", + "help_local_only": "Använd inte Tor (endast för utveckling)", + "help_stay_open": "Fortsätt dela efter första nedladdning", + "help_shutdown_timeout": "Avbryt delning efter ett bestämt antal sekunder", + "help_stealth": "Använd klient-auktorisering (avancerat)", + "help_receive": "Ta emot delningar istället för att skicka dem", + "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", + "help_filename": "Lista filer och mappar att dela", + "help_config": "Egenvald sökväg för JSON konfigurationsfil (valfri)", + "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att påbörja delning", + "gui_add": "Lägg till", + "gui_delete": "Radera", + "gui_choose_items": "Välj", + "gui_share_start_server": "Påbörja delning", + "gui_share_stop_server": "Avbryt delning", + "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "Starta Mottagarläge", + "gui_receive_stop_server": "Avsluta Mottagarläge", + "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer avslutas kl {}", + "gui_copy_url": "Kopiera Adress", + "gui_copy_hidservauth": "Kopiera HidServAuth", + "gui_downloads": "Nedladdningshistorik", + "gui_no_downloads": "Inga Nedladdningar Än", + "gui_canceled": "Avbruten", + "gui_copied_url_title": "OnionShare Adress Kopierad", + "gui_copied_url": "OnionShare adress kopierad till urklipp", + "gui_copied_hidservauth_title": "HidServAuth Kopierad", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/tr.json b/share/locale/tr.json index 68807410..ae6a7058 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -1,7 +1,7 @@ { - "preparing_files": "Paylaşmak için dosyalar hazırlanıyor.", - "give_this_url": "Dosyayı gönderdiğin kişiye bu URL'i verin:", - "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", + "preparing_files": "Sıkıştırma dosyaları.", + "give_this_url": "Bu adresi alıcıya verin:", + "ctrlc_to_stop": "Sunucuyu durdurmak için, Ctrl-C basın", "not_a_file": "{0:s} dosya değil.", "other_page_loaded": "Diğer sayfa yüklendi", "closing_automatically": "İndirme işlemi tamamlandığı için kendiliğinden durduruluyor", @@ -21,5 +21,6 @@ "gui_canceled": "İptal edilen", "gui_copied_url": "Panoya kopyalanan URL", "gui_please_wait": "Lütfen bekleyin...", - "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%" + "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%", + "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla." } diff --git a/share/locale/wo.json b/share/locale/wo.json new file mode 100644 index 00000000..a67a5f75 --- /dev/null +++ b/share/locale/wo.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/yo.json b/share/locale/yo.json new file mode 100644 index 00000000..25cd5c48 --- /dev/null +++ b/share/locale/yo.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json new file mode 100644 index 00000000..958bfb3c --- /dev/null +++ b/share/locale/zh_Hans.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "退出", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "添加", + "gui_delete": "删除", + "gui_choose_items": "选择", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "取消", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "退出", + "gui_quit_warning_dont_quit": "取消", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "设置中", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "检查新版本", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "从不", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "常规设置", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "控制端口", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "密码", + "gui_settings_password_label": "密码", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "保存", + "gui_settings_button_cancel": "取消", + "gui_settings_button_help": "帮助", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "是的", + "gui_tor_connection_ask_quit": "退出", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "分享", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "正在接收", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "分享轨迹", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "清除所有", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "首选语言", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/zh_Hant.json b/share/locale/zh_Hant.json new file mode 100644 index 00000000..c24d13ce --- /dev/null +++ b/share/locale/zh_Hant.json @@ -0,0 +1,185 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "large_filesize": "", + "systray_menu_exit": "離開", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "新增", + "gui_delete": "刪除", + "gui_choose_items": "選擇", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "取消", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "離開", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "設定", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "檢查新版本", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "不使用", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "一般設定", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "管理連接埠", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "保存", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "協助", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "離開", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "分享", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "收取", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "瀏覽", + "gui_settings_receive_allow_receiver_shutdown_checkbox": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 21019509896979fb0da72cc04ffe537ec1a052cf Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Thu, 27 Dec 2018 11:54:14 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 4abe35e1..92987bd7 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,9 +1,9 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "ضغط الملفات", - "give_this_url": "أعط هذا العنوان للمستقبل", + "preparing_files": "ضغط الملفات.", + "give_this_url": "أعط هذا العنوان للمتلقي:", "give_this_url_stealth": "", - "give_this_url_receive": "", + "give_this_url_receive": "إعط هذا العنوان للمُرسِل:", "give_this_url_receive_stealth": "", "ctrlc_to_stop": "", "not_a_file": "", -- cgit v1.2.3-54-g00ecf From f56a7c839ae6e41b136e01b972d98e7382647349 Mon Sep 17 00:00:00 2001 From: MA Date: Tue, 25 Dec 2018 04:24:56 +0000 Subject: Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ --- share/locale/bg.json | 358 +++++++++++++++++++++++++-------------------------- 1 file changed, 179 insertions(+), 179 deletions(-) diff --git a/share/locale/bg.json b/share/locale/bg.json index 25cd5c48..660effe2 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -1,185 +1,185 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", + "config_onion_service": "Създаване на onion служба на порт {0:d}.", + "preparing_files": "Архивира документи.", + "give_this_url": "Дай този адрес на получателя:", + "give_this_url_stealth": "Дай този адрес и HidServAuth реда на получателя:", + "give_this_url_receive": "Дай този адрес на подателя:", + "give_this_url_receive_stealth": "Дай този адрес и HidServAuth на подателя:", + "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", + "not_a_file": "{10: s) не е валиден документ.", + "not_a_readable_file": "{0: s) не е четаем файл.", + "no_available_port": "Свободен порт не бе намерен, за да може onion service да бъде стартиран.", + "other_page_loaded": "Адресът е зареден", + "close_on_timeout": "Спряно, защото таймерът с автоматично спиране приключи", + "closing_automatically": "Спряно, защото свалянето приключи", + "timeout_download_still_running": "Изчакване на свалянето да приключи", + "timeout_upload_still_running": "Изчакаване ъплоудът да приключи", + "large_filesize": "Предупреждение: изпращане на голям дял може да отнеме часове", + "systray_menu_exit": "Изход", + "systray_download_started_title": "OnionShare сваляне започна", + "systray_download_started_message": "Потребител започна да сваля файловете Ви", + "systray_download_completed_title": "OnionShare свалянето приключи", + "systray_download_completed_message": "Потребителят приключи с изтегляне на Вашите файлове", + "systray_download_canceled_title": "OnionShare сваляне е отменено", + "systray_download_canceled_message": "Потребителят отмени свалянето", + "systray_upload_started_title": "OnionShare ъплоуд започна", + "systray_upload_started_message": "Ползвател започна да ъплоудва файлове на компютъра Ви", + "help_local_only": "Не използвайте Тор (само за разработване)", + "help_stay_open": "Продължи споделянето след първото изтегляне", + "help_shutdown_timeout": "Спри споделянето след дадено количество секунди", + "help_stealth": "Използвай клиент авторизация (напреднал)", + "help_receive": "Получаване на дялове вместо изпращане", + "help_debug": "Протоколирай OnionShare грешки на stdout и уеб грешки на диск", + "help_filename": "Списък на документи или папки за споделяне", + "help_config": "Персонализирано местоположение на JSON конфигурационен файл (незадължително)", + "gui_drag_and_drop": "Плъзнете и пуснете файлове и папки, \nза да започнете споделяне", + "gui_add": "Добави", + "gui_delete": "Изтриване", + "gui_choose_items": "Избери", + "gui_share_start_server": "Започни споделянето", + "gui_share_stop_server": "Спри споделянето", + "gui_share_stop_server_shutdown_timeout": "Спри споделянето ({} остават)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Tаймерът с автоматично спиране терминира в {}", + "gui_receive_start_server": "Стартирай получаващ режим", + "gui_receive_stop_server": "Спри получаващия режим", + "gui_receive_stop_server_shutdown_timeout": "Спри получаващия режим ({} остават)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Автоматично спиращияt таймер спира в {}", + "gui_copy_url": "Копирай адрес", + "gui_copy_hidservauth": "Копирай HidServAuth", + "gui_downloads": "Свали история", + "gui_no_downloads": "Още няма изтегляния", + "gui_canceled": "Отменен", + "gui_copied_url_title": "OnionShare адрес е копиран", + "gui_copied_url": "OnionShare адресът е копиран към клипборда", + "gui_copied_hidservauth_title": "HidServAuth е копиран", + "gui_copied_hidservauth": "HidServAuth ред е копиран към клипборда", + "gui_please_wait": "Започва... - кликни за отменяне", + "gui_download_upload_progress_complete": "% p% (0: s) изтече.", + "gui_download_upload_progress_starting": "{10: s),% p% (изчисляване)", + "gui_download_upload_progress_eta": "{0: s), eta: {1: s),% p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Не толкова бързо", + "gui_share_quit_warning": "Намирате се в процес на изпращане на данни. Сигурни ли сте, че искате да спрете OnionShare?", + "gui_receive_quit_warning": "Намирате се в процес на получаване на файлове. Сигурни ли сте, че искате да спрете OnionShare?", + "gui_quit_warning_quit": "Изход", + "gui_quit_warning_dont_quit": "Отказ", + "error_rate_limit": "Някой е направил прекалено много грешни опити за адреса ти, което означава, че може да се опитват да го отгатнат, така че OnionShare спря сървъра. Стартирайте споделянето отново и изпратете нов адрес на получателя за споделяне.", + "zip_progress_bar_format": "Компресира:% p%", + "error_stealth_not_supported": "За да използвате ауторизация на клиента Ви трябва поне Tor 0.2.9.1-alpha (или на браузъра 6.5) и python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare изисква поне Tor 0.2.7.1 и python3-stem 1.4.0.", + "gui_settings_window_title": "Настройки", + "gui_settings_whats_this": "Какво е това?", + "gui_settings_stealth_option": "Използвай клиент ауторизация (наследствен)", + "gui_settings_stealth_hidservauth_string": "След като Вашия частен ключ бе запазен за повторна употреба, можете сега да кликнете, за да копирате Вашия HidServAuth.", + "gui_settings_autoupdate_label": "Провери за нова версия", + "gui_settings_autoupdate_option": "Уведоми ме, когато е налице нова версия", + "gui_settings_autoupdate_timestamp": "Последна проверка: {}", + "gui_settings_autoupdate_timestamp_never": "Никога", + "gui_settings_autoupdate_check_button": "Проверете за нова версия", + "gui_settings_general_label": "Общи настройки", + "gui_settings_sharing_label": "Настройки на споделяне", + "gui_settings_close_after_first_download_option": "Спри споделянето след първото изтегляне", + "gui_settings_connection_type_label": "Как OnionShare да се свързже с Тор?", + "gui_settings_connection_type_bundled_option": "Използвай Тор версия, вградена в OnionShare", + "gui_settings_connection_type_automatic_option": "Опит за автоматична конфигурация с Тор браузъра", + "gui_settings_connection_type_control_port_option": "Свържете използвайки контролен порт", + "gui_settings_connection_type_socket_file_option": "Свържете се използвайки сокет", + "gui_settings_connection_type_test_button": "Тест на връзката с Тор", + "gui_settings_control_port_label": "Контролен порт", + "gui_settings_socket_file_label": "Сокет файл", + "gui_settings_socks_label": "SOCKS порт", + "gui_settings_authenticate_label": "Настройки на Tor за удостоверяване на автентичността", + "gui_settings_authenticate_no_auth_option": "Без автентикация или cookie автентикация", + "gui_settings_authenticate_password_option": "Парола", + "gui_settings_password_label": "Парола", + "gui_settings_tor_bridges": "Поддръжка на Тор мост", + "gui_settings_tor_bridges_no_bridges_radio_option": "Не използвайте мостове", + "gui_settings_tor_bridges_obfs4_radio_option": "Използвайте вградените obfs4 pluggable транспорти", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Използвайте вградените obfs4 pluggable транспорти (изисква obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Използвайте вградените meek_lite (Azure) pluggable транспорти", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Използвайте вградените meek_lite (Azure) pluggable транспорти (изисква obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Предупреждение: meek_lite мостовета са много скъпи за проекта Тор.

    Използвайте ги само, ако не могат да се свържат пряко чрез obfs4 транспорти или други нормални мостове с Тор.", + "gui_settings_tor_bridges_custom_radio_option": "Използвайте персонализирани мостове", + "gui_settings_tor_bridges_custom_label": "Може намерите мостове на https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Нито един от добавените от Вас мостове работят.\nПроверете ги отново или добавете други.", + "gui_settings_button_save": "Запазване", + "gui_settings_button_cancel": "Отказ", + "gui_settings_button_help": "Помощ", + "gui_settings_shutdown_timeout_checkbox": "Използвайте автоматично спиращия таймер", + "gui_settings_shutdown_timeout": "Спри дела на:", + "settings_error_unknown": "Не мога да се свържа с Тор контролера, защото Вашите настройки не правят смисъл.", + "settings_error_automatic": "Не мога да се свържа с Тор контролера. Стартиран ли е Тор браузерът във фонов режим (достъпен от torproject. org)?", + "settings_error_socket_port": "Не мога да се свържа с Тор контролера в {}:{}.", + "settings_error_socket_file": "Не мога да се свържа с Тор контролера, използвайки сокет файл .{}.", + "settings_error_auth": "Свързан с {}:{}, но не може да се идентифицира. Може би това не е Тор контролер?", + "settings_error_missing_password": "Свързан с Тор контролер, но той изисква парола за идентификация.", + "settings_error_unreadable_cookie_file": "Свързан с Тор контролер, но паролата може да е грешна, или на Вашият потребител не е позволено да чете бисквитката файл.", + "settings_error_bundled_tor_not_supported": "Използване на Тор версия, идваща с OnionShare не работи в режим на разработчик под Windows или macOS.", + "settings_error_bundled_tor_timeout": "Oтнема прекалено дълго време да се свържа с Тор. Може би не сте свързани с интернет или системния часовник е неточен?", + "settings_error_bundled_tor_broken": "OnionShare не можа да се свърже с Тор във фонов режим:\n{}", + "settings_test_success": "Свързан с Тор контролер.\n\nТор версия: {}\nПоддържа ephemeral onion services: {}\nПоддържа клиент автентикация: {}\nПоддържа следваща генерация .onion адреси: {}", + "error_tor_protocol_error": "Станала е грешка с Тор: {}", + "error_tor_protocol_error_unknown": "Имаше неизвестен грешка с Тор", + "error_invalid_private_key": "Този тип частен ключ е неподдържан", + "connecting_to_tor": "Свързване към Тор мрежата", + "update_available": "Има нов OnionShare. Кликнете тук, за да го изтеглите.

    Вие използвате {}, а последният е {}.", + "update_error_check_error": "Не може да се провери за нови версии: OnionShare сайтът казва, че не разпознава последната версия '{}'…", + "update_error_invalid_latest_version": "Не мОГ да проверя за нова версия: Може би не сте свързани към Тор, или OnionShare уебсайтът е изключен?", + "update_not_available": "Вие изпозвате псоледната версия на OnionShare.", + "gui_tor_connection_ask": "Отворете настройките, за да възстановите връзката с Тор?", + "gui_tor_connection_ask_open_settings": "Да", + "gui_tor_connection_ask_quit": "Изход", + "gui_tor_connection_error_settings": "Опитайте се да промените в настройките как OnionShare се свързва с Тор.", + "gui_tor_connection_canceled": "Не може да се установи връзка с Тор.\n\nУверете се, че имате връзка с интернтет, след което отново отворете OnionShare и пренастройте връзката с Тор.", + "gui_tor_connection_lost": "Връзката с Тор е прекъсната.", + "gui_server_started_after_timeout": "Аввтоматично спиращият таъмер спря преди сървърът да стартира.\nМоля направете нов дял.", + "gui_server_timeout_expired": "Автоматично спиращият таймер спря.\nМоля актуализирайте за да започнете споделяне.", + "share_via_onionshare": "Споделете го чрез OnionShare", "gui_use_legacy_v2_onions_checkbox": "", "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", + "gui_share_url_description": "Всеки с този OnionShare адрес може да свали Вашите файлове използвайки Тор браузера: ", + "gui_receive_url_description": "Всеки с този OnionShare адрес може да качи файлове на Вашия компютър, използвайки Тор браузера: ", "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", + "gui_url_label_stay_open": "Тази част няма да спре автоматично.", + "gui_url_label_onetime": "Тази част ще спре след първото изпълнение.", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_status_indicator_share_stopped": "Готово за споделяне", + "gui_status_indicator_share_working": "Започва…", + "gui_status_indicator_share_started": "Споделяне", + "gui_status_indicator_receive_stopped": "Готово за приемане", + "gui_status_indicator_receive_working": "Започва…", + "gui_status_indicator_receive_started": "Получаване", + "gui_file_info": "{} файлове, {}", + "gui_file_info_single": "{} файл, {}", + "history_in_progress_tooltip": "{} е в прогрес", + "history_completed_tooltip": "{} завършено", + "info_in_progress_uploads_tooltip": "{} качване(та) в прогрес", + "info_completed_uploads_tooltip": "{} ъплоудът(ите) е(са) завършен(и)", + "error_cannot_create_downloads_dir": "Не мога да създам папка за режим на приемане: {}", + "receive_mode_downloads_dir": "Документи, изпратени до Вас, се появяват в тази папка: {}", + "receive_mode_warning": "Предупреждение: Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър. Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "gui_receive_mode_warning": "Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър.

    Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "receive_mode_upload_starting": "Ъплоуд на общ размер {} започва", + "receive_mode_received_file": "Получено: {}", + "gui_mode_share_button": "Споделяне на файлове", + "gui_mode_receive_button": "Получи документи", + "gui_settings_receiving_label": "Настройки на получаване", + "gui_settings_downloads_label": "Запазете файлове в", + "gui_settings_downloads_button": "Разглеждане", + "gui_settings_public_mode_checkbox": "Публичен режим", + "systray_close_server_title": "OnionShare сървърът приключи", + "systray_close_server_message": "Един ползвател затвори сървъра", + "systray_page_loaded_title": "OnionShare страницата е заредена", + "systray_download_page_loaded_message": "Един ползвател зареди свалената страница", + "systray_upload_page_loaded_message": "Един ползвател зареди качената страница", + "gui_uploads": "Ъплоуд история", + "gui_no_uploads": "Все още няма ъплоуди", + "gui_clear_history": "Изтрий всичко", + "gui_upload_in_progress": "Ъплоудът започна", + "gui_upload_finished_range": "Качен {} на {}", + "gui_upload_finished": "Качен {}", + "gui_download_in_progress": "Изтеглянето започна {}", + "gui_open_folder_error_nautilus": "Не може да бъде отворена папка, защото \"nautilus\" не е на разположение. Файлът е тук: {}", + "gui_settings_language_label": "Предпочитан език", + "gui_settings_language_changed_notice": "Рестартирайте OnionShare Вашата промяна на език да има ефект." } -- cgit v1.2.3-54-g00ecf From b47db255186c4957078475a7a67669099b6aef7a Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:47:34 +0000 Subject: Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ --- share/locale/id.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/id.json b/share/locale/id.json index 24da50a0..0557aca9 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "Dibatalkan", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From b68b6899f1edb3eaa05f380628f92a0ea021ad59 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:46:42 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index a6f6d265..8d4f5bf3 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -17,7 +17,7 @@ "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", "gui_copy_url": "Copia l' URL", - "gui_downloads": "Cronologia Dei Download", + "gui_downloads": "Cronologia Dei Downloads", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", "gui_please_wait": "Attendere prego...", -- cgit v1.2.3-54-g00ecf From 24039961df3fd862b3890d2969968bed2a5709c3 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:47:37 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/no.json b/share/locale/no.json index 4d474cea..381b4e84 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,6 +1,6 @@ { "give_this_url": "Gi denne adressen til mottakeren:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", -- cgit v1.2.3-54-g00ecf From 583c83c7f6d0f953e3f59b5935eeb3b0cffa2095 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:46:04 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index 9b15a20d..d45ea27c 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "لغو شده", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From 587af6c6cdec77ab3c6888762a3e84583a4b07ed Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:47:55 +0000 Subject: Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ --- share/locale/pl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/pl.json b/share/locale/pl.json index c45af162..d24a0818 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "Wybierz...", + "gui_settings_downloads_button": "Przeglądaj", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", -- cgit v1.2.3-54-g00ecf From 38ee78b045a7a2a450ba366a022a851fb1696eb7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 10:45:53 +0000 Subject: Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ --- share/locale/ro.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/ro.json b/share/locale/ro.json index b46f0b5d..54750c24 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "Anulat", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From 8aa41d727403269433ca76ab5306d3e86b894022 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 28 Dec 2018 08:44:33 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 93a86673..e18c54a0 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -9,7 +9,7 @@ "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastre y suelte archivos y carpetas\npara empezar a compartir", + "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", @@ -37,7 +37,7 @@ "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", - "gui_settings_stealth_hidservauth_string": "Habiendo guardado su clave privada para volver a utilizarla, ahora puede\nhacer clic para copiar tu HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", @@ -48,7 +48,7 @@ "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", "settings_saved": "Ajustes guardados en {}", "give_this_url_receive": "Dale esta dirección al remitente:", - "give_this_url_receive_stealth": "Dar esta dirección y HidServAuth al remitente:", + "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:", "not_a_readable_file": "{0:s} no es un archivo legible.", "systray_menu_exit": "Salir", "systray_download_started_title": "Iniciada la descarga de OnionShare", @@ -70,15 +70,15 @@ "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", - "settings_error_unknown": "No se puede conectar al controlador Tor porque su configuración no tiene sentido.", + "settings_error_unknown": "No se puede conectar al controlador Tor porque tu configuración no tiene sentido.", "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?", "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", - "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o su usuario no tiene permiso para leer el archivo cookie.", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o tu usuario no tiene permiso para leer el archivo cookie.", "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", - "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tiene un reloj impreciso?", + "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", @@ -87,17 +87,17 @@ "connecting_to_tor": "Conectando a la red de Tor", "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Estás usando {} y el último es {}.", "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…", - "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no está conectado a Tor, o el sitio web de OnionShare está caído?", - "update_not_available": "Está ejecutando la última versión de OnionShare.", + "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Estás ejecutando la última versión de OnionShare.", "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?", "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Salir", - "gui_tor_connection_error_settings": "Intente cambiando la forma en que OnionShare se conecta a la red Tor en su configuración.", - "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar su conexión a Tor.", + "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor cree nueva conexión compartida.", - "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualícelo para comenzar a compartir.", - "share_via_onionshare": "Compártalo con OnionShare", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea nueva conexión compartida.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", + "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", @@ -105,7 +105,7 @@ "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", - "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", "gui_status_indicator_share_stopped": "Listo para compartir", "gui_status_indicator_share_working": "Comenzando.…", "gui_status_indicator_share_started": "Compartir", @@ -118,8 +118,8 @@ "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", "info_in_progress_uploads_tooltip": "{} subida(s) en curso", "info_completed_uploads_tooltip": "{} subida(s) completada(s)", - "receive_mode_downloads_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", - "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abre, podrían tomar el control de su ordenador. Abra sólo cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "receive_mode_downloads_dir": "Los archivos que te envíen aparecerán en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a tu ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíe, o si sabes lo que estás haciendo.", "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%", @@ -161,8 +161,8 @@ "history_completed_tooltip": "{} completado", "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}", "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", - "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", - "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de tu ordenador si los abres. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "receive_mode_upload_starting": "La subida con un tamaño total de {} está comenzando ahora", "receive_mode_received_file": "Recibido: {}", "gui_mode_share_button": "Compartir archivos", "gui_mode_receive_button": "Recibir archivos", @@ -184,7 +184,7 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", + "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", "timeout_upload_still_running": "Esperando a que se complete la subida" } -- cgit v1.2.3-54-g00ecf From eba22eb1ea64f7afd8da90ffa836eadb1d03d4fc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Dec 2018 09:28:46 -0800 Subject: Weblate merge, again (#857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Added translation using Weblate (Catalan) * Added translation using Weblate (Georgian) * Added translation using Weblate (Greek) * Added translation using Weblate (Hungarian) * Added translation using Weblate (Icelandic) * Added translation using Weblate (Irish) * Added translation using Weblate (Persian) * Added translation using Weblate (Punjabi) * Added translation using Weblate (Romanian) * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Added translation using Weblate (Portuguese (Brazil)) * Added translation using Weblate (Portuguese (Portugal)) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Deleted translation using Weblate (Portuguese) * Added translation using Weblate (Arabic) * Added translation using Weblate (Indonesian) * Added translation using Weblate (Macedonian) * Added translation using Weblate (Polish) * Added translation using Weblate (Slovenian) * Added translation using Weblate (Tachelhit) * Added translation using Weblate (Wolof) * Added translation using Weblate (Chinese (Simplified)) * Added translation using Weblate (Chinese (Traditional)) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Irish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ga/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Korean) * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Bengali) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Gujarati) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/gu/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Esperanto) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/eo/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Georgian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ka/ * Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ * Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Macedonian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/mk/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Punjabi) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pa/ * Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Slovenian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sl/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Deleted translation using Weblate (Tachelhit) * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ * Update from Weblate (#4) * Translated using Weblate * Added translation using Weblate (Amharic) * Added translation using Weblate (Luganda) * Added translation using Weblate (Yoruba) --- share/locale/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 3ecc763b..e8910d06 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -23,7 +23,7 @@ "help_stay_open": "Blijven delen na afronden van eerste download", "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", - "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", -- cgit v1.2.3-54-g00ecf From 0fbea2693cc598161792ca421d803d9fac73e567 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 28 Dec 2018 09:42:24 -0800 Subject: Weblate merge, final (#858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Added translation using Weblate (Catalan) * Added translation using Weblate (Georgian) * Added translation using Weblate (Greek) * Added translation using Weblate (Hungarian) * Added translation using Weblate (Icelandic) * Added translation using Weblate (Irish) * Added translation using Weblate (Persian) * Added translation using Weblate (Punjabi) * Added translation using Weblate (Romanian) * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Portuguese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Added translation using Weblate (Portuguese (Brazil)) * Added translation using Weblate (Portuguese (Portugal)) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Deleted translation using Weblate (Portuguese) * Added translation using Weblate (Arabic) * Added translation using Weblate (Indonesian) * Added translation using Weblate (Macedonian) * Added translation using Weblate (Polish) * Added translation using Weblate (Slovenian) * Added translation using Weblate (Tachelhit) * Added translation using Weblate (Wolof) * Added translation using Weblate (Chinese (Simplified)) * Added translation using Weblate (Chinese (Traditional)) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Irish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ga/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Korean) * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Added translation using Weblate (Bengali) * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Gujarati) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/gu/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Esperanto) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/eo/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Georgian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ka/ * Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ * Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Macedonian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/mk/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Punjabi) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pa/ * Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Slovenian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sl/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Deleted translation using Weblate (Tachelhit) * Translated using Weblate (Portuguese (Portugal)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_PT/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Chinese (Traditional)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hant/ * Update from Weblate (#4) * Translated using Weblate * Added translation using Weblate (Amharic) * Added translation using Weblate (Luganda) * Added translation using Weblate (Yoruba) * Translated using Weblate (Icelandic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/is/ * Translated using Weblate (Amharic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/am/ * Translated using Weblate (Hungarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/hu/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Added translation using Weblate (Swedish) * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Added translation using Weblate (Bulgarian) * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Indonesian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/id/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Romanian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ro/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/ar.json | 6 +- share/locale/bg.json | 358 +++++++++++++++++++++++++-------------------------- share/locale/es.json | 38 +++--- share/locale/fa.json | 2 +- share/locale/id.json | 2 +- share/locale/it.json | 2 +- share/locale/nl.json | 2 +- share/locale/no.json | 2 +- share/locale/pl.json | 2 +- share/locale/ro.json | 2 +- 10 files changed, 208 insertions(+), 208 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 4abe35e1..92987bd7 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,9 +1,9 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "ضغط الملفات", - "give_this_url": "أعط هذا العنوان للمستقبل", + "preparing_files": "ضغط الملفات.", + "give_this_url": "أعط هذا العنوان للمتلقي:", "give_this_url_stealth": "", - "give_this_url_receive": "", + "give_this_url_receive": "إعط هذا العنوان للمُرسِل:", "give_this_url_receive_stealth": "", "ctrlc_to_stop": "", "not_a_file": "", diff --git a/share/locale/bg.json b/share/locale/bg.json index 25cd5c48..660effe2 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -1,185 +1,185 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", + "config_onion_service": "Създаване на onion служба на порт {0:d}.", + "preparing_files": "Архивира документи.", + "give_this_url": "Дай този адрес на получателя:", + "give_this_url_stealth": "Дай този адрес и HidServAuth реда на получателя:", + "give_this_url_receive": "Дай този адрес на подателя:", + "give_this_url_receive_stealth": "Дай този адрес и HidServAuth на подателя:", + "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", + "not_a_file": "{10: s) не е валиден документ.", + "not_a_readable_file": "{0: s) не е четаем файл.", + "no_available_port": "Свободен порт не бе намерен, за да може onion service да бъде стартиран.", + "other_page_loaded": "Адресът е зареден", + "close_on_timeout": "Спряно, защото таймерът с автоматично спиране приключи", + "closing_automatically": "Спряно, защото свалянето приключи", + "timeout_download_still_running": "Изчакване на свалянето да приключи", + "timeout_upload_still_running": "Изчакаване ъплоудът да приключи", + "large_filesize": "Предупреждение: изпращане на голям дял може да отнеме часове", + "systray_menu_exit": "Изход", + "systray_download_started_title": "OnionShare сваляне започна", + "systray_download_started_message": "Потребител започна да сваля файловете Ви", + "systray_download_completed_title": "OnionShare свалянето приключи", + "systray_download_completed_message": "Потребителят приключи с изтегляне на Вашите файлове", + "systray_download_canceled_title": "OnionShare сваляне е отменено", + "systray_download_canceled_message": "Потребителят отмени свалянето", + "systray_upload_started_title": "OnionShare ъплоуд започна", + "systray_upload_started_message": "Ползвател започна да ъплоудва файлове на компютъра Ви", + "help_local_only": "Не използвайте Тор (само за разработване)", + "help_stay_open": "Продължи споделянето след първото изтегляне", + "help_shutdown_timeout": "Спри споделянето след дадено количество секунди", + "help_stealth": "Използвай клиент авторизация (напреднал)", + "help_receive": "Получаване на дялове вместо изпращане", + "help_debug": "Протоколирай OnionShare грешки на stdout и уеб грешки на диск", + "help_filename": "Списък на документи или папки за споделяне", + "help_config": "Персонализирано местоположение на JSON конфигурационен файл (незадължително)", + "gui_drag_and_drop": "Плъзнете и пуснете файлове и папки, \nза да започнете споделяне", + "gui_add": "Добави", + "gui_delete": "Изтриване", + "gui_choose_items": "Избери", + "gui_share_start_server": "Започни споделянето", + "gui_share_stop_server": "Спри споделянето", + "gui_share_stop_server_shutdown_timeout": "Спри споделянето ({} остават)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Tаймерът с автоматично спиране терминира в {}", + "gui_receive_start_server": "Стартирай получаващ режим", + "gui_receive_stop_server": "Спри получаващия режим", + "gui_receive_stop_server_shutdown_timeout": "Спри получаващия режим ({} остават)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Автоматично спиращияt таймер спира в {}", + "gui_copy_url": "Копирай адрес", + "gui_copy_hidservauth": "Копирай HidServAuth", + "gui_downloads": "Свали история", + "gui_no_downloads": "Още няма изтегляния", + "gui_canceled": "Отменен", + "gui_copied_url_title": "OnionShare адрес е копиран", + "gui_copied_url": "OnionShare адресът е копиран към клипборда", + "gui_copied_hidservauth_title": "HidServAuth е копиран", + "gui_copied_hidservauth": "HidServAuth ред е копиран към клипборда", + "gui_please_wait": "Започва... - кликни за отменяне", + "gui_download_upload_progress_complete": "% p% (0: s) изтече.", + "gui_download_upload_progress_starting": "{10: s),% p% (изчисляване)", + "gui_download_upload_progress_eta": "{0: s), eta: {1: s),% p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Не толкова бързо", + "gui_share_quit_warning": "Намирате се в процес на изпращане на данни. Сигурни ли сте, че искате да спрете OnionShare?", + "gui_receive_quit_warning": "Намирате се в процес на получаване на файлове. Сигурни ли сте, че искате да спрете OnionShare?", + "gui_quit_warning_quit": "Изход", + "gui_quit_warning_dont_quit": "Отказ", + "error_rate_limit": "Някой е направил прекалено много грешни опити за адреса ти, което означава, че може да се опитват да го отгатнат, така че OnionShare спря сървъра. Стартирайте споделянето отново и изпратете нов адрес на получателя за споделяне.", + "zip_progress_bar_format": "Компресира:% p%", + "error_stealth_not_supported": "За да използвате ауторизация на клиента Ви трябва поне Tor 0.2.9.1-alpha (или на браузъра 6.5) и python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare изисква поне Tor 0.2.7.1 и python3-stem 1.4.0.", + "gui_settings_window_title": "Настройки", + "gui_settings_whats_this": "Какво е това?", + "gui_settings_stealth_option": "Използвай клиент ауторизация (наследствен)", + "gui_settings_stealth_hidservauth_string": "След като Вашия частен ключ бе запазен за повторна употреба, можете сега да кликнете, за да копирате Вашия HidServAuth.", + "gui_settings_autoupdate_label": "Провери за нова версия", + "gui_settings_autoupdate_option": "Уведоми ме, когато е налице нова версия", + "gui_settings_autoupdate_timestamp": "Последна проверка: {}", + "gui_settings_autoupdate_timestamp_never": "Никога", + "gui_settings_autoupdate_check_button": "Проверете за нова версия", + "gui_settings_general_label": "Общи настройки", + "gui_settings_sharing_label": "Настройки на споделяне", + "gui_settings_close_after_first_download_option": "Спри споделянето след първото изтегляне", + "gui_settings_connection_type_label": "Как OnionShare да се свързже с Тор?", + "gui_settings_connection_type_bundled_option": "Използвай Тор версия, вградена в OnionShare", + "gui_settings_connection_type_automatic_option": "Опит за автоматична конфигурация с Тор браузъра", + "gui_settings_connection_type_control_port_option": "Свържете използвайки контролен порт", + "gui_settings_connection_type_socket_file_option": "Свържете се използвайки сокет", + "gui_settings_connection_type_test_button": "Тест на връзката с Тор", + "gui_settings_control_port_label": "Контролен порт", + "gui_settings_socket_file_label": "Сокет файл", + "gui_settings_socks_label": "SOCKS порт", + "gui_settings_authenticate_label": "Настройки на Tor за удостоверяване на автентичността", + "gui_settings_authenticate_no_auth_option": "Без автентикация или cookie автентикация", + "gui_settings_authenticate_password_option": "Парола", + "gui_settings_password_label": "Парола", + "gui_settings_tor_bridges": "Поддръжка на Тор мост", + "gui_settings_tor_bridges_no_bridges_radio_option": "Не използвайте мостове", + "gui_settings_tor_bridges_obfs4_radio_option": "Използвайте вградените obfs4 pluggable транспорти", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Използвайте вградените obfs4 pluggable транспорти (изисква obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Използвайте вградените meek_lite (Azure) pluggable транспорти", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Използвайте вградените meek_lite (Azure) pluggable транспорти (изисква obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Предупреждение: meek_lite мостовета са много скъпи за проекта Тор.

    Използвайте ги само, ако не могат да се свържат пряко чрез obfs4 транспорти или други нормални мостове с Тор.", + "gui_settings_tor_bridges_custom_radio_option": "Използвайте персонализирани мостове", + "gui_settings_tor_bridges_custom_label": "Може намерите мостове на https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Нито един от добавените от Вас мостове работят.\nПроверете ги отново или добавете други.", + "gui_settings_button_save": "Запазване", + "gui_settings_button_cancel": "Отказ", + "gui_settings_button_help": "Помощ", + "gui_settings_shutdown_timeout_checkbox": "Използвайте автоматично спиращия таймер", + "gui_settings_shutdown_timeout": "Спри дела на:", + "settings_error_unknown": "Не мога да се свържа с Тор контролера, защото Вашите настройки не правят смисъл.", + "settings_error_automatic": "Не мога да се свържа с Тор контролера. Стартиран ли е Тор браузерът във фонов режим (достъпен от torproject. org)?", + "settings_error_socket_port": "Не мога да се свържа с Тор контролера в {}:{}.", + "settings_error_socket_file": "Не мога да се свържа с Тор контролера, използвайки сокет файл .{}.", + "settings_error_auth": "Свързан с {}:{}, но не може да се идентифицира. Може би това не е Тор контролер?", + "settings_error_missing_password": "Свързан с Тор контролер, но той изисква парола за идентификация.", + "settings_error_unreadable_cookie_file": "Свързан с Тор контролер, но паролата може да е грешна, или на Вашият потребител не е позволено да чете бисквитката файл.", + "settings_error_bundled_tor_not_supported": "Използване на Тор версия, идваща с OnionShare не работи в режим на разработчик под Windows или macOS.", + "settings_error_bundled_tor_timeout": "Oтнема прекалено дълго време да се свържа с Тор. Може би не сте свързани с интернет или системния часовник е неточен?", + "settings_error_bundled_tor_broken": "OnionShare не можа да се свърже с Тор във фонов режим:\n{}", + "settings_test_success": "Свързан с Тор контролер.\n\nТор версия: {}\nПоддържа ephemeral onion services: {}\nПоддържа клиент автентикация: {}\nПоддържа следваща генерация .onion адреси: {}", + "error_tor_protocol_error": "Станала е грешка с Тор: {}", + "error_tor_protocol_error_unknown": "Имаше неизвестен грешка с Тор", + "error_invalid_private_key": "Този тип частен ключ е неподдържан", + "connecting_to_tor": "Свързване към Тор мрежата", + "update_available": "Има нов OnionShare. Кликнете тук, за да го изтеглите.

    Вие използвате {}, а последният е {}.", + "update_error_check_error": "Не може да се провери за нови версии: OnionShare сайтът казва, че не разпознава последната версия '{}'…", + "update_error_invalid_latest_version": "Не мОГ да проверя за нова версия: Може би не сте свързани към Тор, или OnionShare уебсайтът е изключен?", + "update_not_available": "Вие изпозвате псоледната версия на OnionShare.", + "gui_tor_connection_ask": "Отворете настройките, за да възстановите връзката с Тор?", + "gui_tor_connection_ask_open_settings": "Да", + "gui_tor_connection_ask_quit": "Изход", + "gui_tor_connection_error_settings": "Опитайте се да промените в настройките как OnionShare се свързва с Тор.", + "gui_tor_connection_canceled": "Не може да се установи връзка с Тор.\n\nУверете се, че имате връзка с интернтет, след което отново отворете OnionShare и пренастройте връзката с Тор.", + "gui_tor_connection_lost": "Връзката с Тор е прекъсната.", + "gui_server_started_after_timeout": "Аввтоматично спиращият таъмер спря преди сървърът да стартира.\nМоля направете нов дял.", + "gui_server_timeout_expired": "Автоматично спиращият таймер спря.\nМоля актуализирайте за да започнете споделяне.", + "share_via_onionshare": "Споделете го чрез OnionShare", "gui_use_legacy_v2_onions_checkbox": "", "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", + "gui_share_url_description": "Всеки с този OnionShare адрес може да свали Вашите файлове използвайки Тор браузера: ", + "gui_receive_url_description": "Всеки с този OnionShare адрес може да качи файлове на Вашия компютър, използвайки Тор браузера: ", "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", + "gui_url_label_stay_open": "Тази част няма да спре автоматично.", + "gui_url_label_onetime": "Тази част ще спре след първото изпълнение.", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_status_indicator_share_stopped": "Готово за споделяне", + "gui_status_indicator_share_working": "Започва…", + "gui_status_indicator_share_started": "Споделяне", + "gui_status_indicator_receive_stopped": "Готово за приемане", + "gui_status_indicator_receive_working": "Започва…", + "gui_status_indicator_receive_started": "Получаване", + "gui_file_info": "{} файлове, {}", + "gui_file_info_single": "{} файл, {}", + "history_in_progress_tooltip": "{} е в прогрес", + "history_completed_tooltip": "{} завършено", + "info_in_progress_uploads_tooltip": "{} качване(та) в прогрес", + "info_completed_uploads_tooltip": "{} ъплоудът(ите) е(са) завършен(и)", + "error_cannot_create_downloads_dir": "Не мога да създам папка за режим на приемане: {}", + "receive_mode_downloads_dir": "Документи, изпратени до Вас, се появяват в тази папка: {}", + "receive_mode_warning": "Предупреждение: Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър. Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "gui_receive_mode_warning": "Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър.

    Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "receive_mode_upload_starting": "Ъплоуд на общ размер {} започва", + "receive_mode_received_file": "Получено: {}", + "gui_mode_share_button": "Споделяне на файлове", + "gui_mode_receive_button": "Получи документи", + "gui_settings_receiving_label": "Настройки на получаване", + "gui_settings_downloads_label": "Запазете файлове в", + "gui_settings_downloads_button": "Разглеждане", + "gui_settings_public_mode_checkbox": "Публичен режим", + "systray_close_server_title": "OnionShare сървърът приключи", + "systray_close_server_message": "Един ползвател затвори сървъра", + "systray_page_loaded_title": "OnionShare страницата е заредена", + "systray_download_page_loaded_message": "Един ползвател зареди свалената страница", + "systray_upload_page_loaded_message": "Един ползвател зареди качената страница", + "gui_uploads": "Ъплоуд история", + "gui_no_uploads": "Все още няма ъплоуди", + "gui_clear_history": "Изтрий всичко", + "gui_upload_in_progress": "Ъплоудът започна", + "gui_upload_finished_range": "Качен {} на {}", + "gui_upload_finished": "Качен {}", + "gui_download_in_progress": "Изтеглянето започна {}", + "gui_open_folder_error_nautilus": "Не може да бъде отворена папка, защото \"nautilus\" не е на разположение. Файлът е тук: {}", + "gui_settings_language_label": "Предпочитан език", + "gui_settings_language_changed_notice": "Рестартирайте OnionShare Вашата промяна на език да има ефект." } diff --git a/share/locale/es.json b/share/locale/es.json index 93a86673..e18c54a0 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -9,7 +9,7 @@ "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", - "gui_drag_and_drop": "Arrastre y suelte archivos y carpetas\npara empezar a compartir", + "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir", "gui_add": "Añadir", "gui_delete": "Eliminar", "gui_choose_items": "Elegir", @@ -37,7 +37,7 @@ "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", - "gui_settings_stealth_hidservauth_string": "Habiendo guardado su clave privada para volver a utilizarla, ahora puede\nhacer clic para copiar tu HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", @@ -48,7 +48,7 @@ "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", "settings_saved": "Ajustes guardados en {}", "give_this_url_receive": "Dale esta dirección al remitente:", - "give_this_url_receive_stealth": "Dar esta dirección y HidServAuth al remitente:", + "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:", "not_a_readable_file": "{0:s} no es un archivo legible.", "systray_menu_exit": "Salir", "systray_download_started_title": "Iniciada la descarga de OnionShare", @@ -70,15 +70,15 @@ "gui_no_downloads": "Ninguna Descarga Todavía", "gui_canceled": "Cancelado", "gui_copied_hidservauth_title": "Se ha copiado el token de HidServAuth", - "settings_error_unknown": "No se puede conectar al controlador Tor porque su configuración no tiene sentido.", + "settings_error_unknown": "No se puede conectar al controlador Tor porque tu configuración no tiene sentido.", "settings_error_automatic": "No se puede conectar al controlador Tor. ¿Se está ejecutando el Navegador Tor (disponible en https://www.torproject.org/) en segundo plano?", "settings_error_socket_port": "No se puede conectar al controlador Tor en {}:{}.", "settings_error_socket_file": "No se puede conectar al controlador Tor usando el archivo de socket {}.", "settings_error_auth": "Conectado a {}:{}, pero no se puede autenticar. ¿Quizás este no sea un controlador Tor?", "settings_error_missing_password": "Conectado al controlador Tor, pero requiere una contraseña para autenticarse.", - "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o su usuario no tiene permiso para leer el archivo cookie.", + "settings_error_unreadable_cookie_file": "Conectado al controlador Tor, pero la contraseña puede estar equivocada, o tu usuario no tiene permiso para leer el archivo cookie.", "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", - "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tiene un reloj impreciso?", + "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", @@ -87,17 +87,17 @@ "connecting_to_tor": "Conectando a la red de Tor", "update_available": "Hay un nuevo OnionShare. Haz clic aquí para obtenerlo.

    Estás usando {} y el último es {}.", "update_error_check_error": "No se ha podido comprobar por nuevas versiones: El sitio web de OnionShare está diciendo que la última versión es la irreconocible '{}'.…", - "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no está conectado a Tor, o el sitio web de OnionShare está caído?", - "update_not_available": "Está ejecutando la última versión de OnionShare.", + "update_error_invalid_latest_version": "No se pudo comprobar nueva versión: ¿Quizás no estás conectado a Tor, o el sitio web de OnionShare está caído?", + "update_not_available": "Estás ejecutando la última versión de OnionShare.", "gui_tor_connection_ask": "¿Abrir la configuración para arrreglar la conexión a Tor?", "gui_tor_connection_ask_open_settings": "Sí", "gui_tor_connection_ask_quit": "Salir", - "gui_tor_connection_error_settings": "Intente cambiando la forma en que OnionShare se conecta a la red Tor en su configuración.", - "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrese de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar su conexión a Tor.", + "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", + "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor cree nueva conexión compartida.", - "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualícelo para comenzar a compartir.", - "share_via_onionshare": "Compártalo con OnionShare", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea nueva conexión compartida.", + "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", + "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", @@ -105,7 +105,7 @@ "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", "gui_url_label_stay_open": "Este recurso compartido no se detendrá automáticamente.", "gui_url_label_onetime": "Este recurso compartido se detendrá después de la primera descarga.", - "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", + "gui_url_label_onetime_and_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactiva la opción \"Usar dirección persistente\" en los ajustes.)", "gui_status_indicator_share_stopped": "Listo para compartir", "gui_status_indicator_share_working": "Comenzando.…", "gui_status_indicator_share_started": "Compartir", @@ -118,8 +118,8 @@ "info_completed_downloads_tooltip": "{} descarga(s) completada(s)", "info_in_progress_uploads_tooltip": "{} subida(s) en curso", "info_completed_uploads_tooltip": "{} subida(s) completada(s)", - "receive_mode_downloads_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", - "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a su ordenador. Algunos archivos, si los abre, podrían tomar el control de su ordenador. Abra sólo cosas de personas en las que confíe, o si sabe lo que está haciendo.", + "receive_mode_downloads_dir": "Los archivos que te envíen aparecerán en esta carpeta: {}", + "receive_mode_warning": "Advertencia: El modo de recepción permite a la gente subir archivos a tu ordenador. Algunos archivos, si los abres, podrían tomar el control de tu ordenador. Abre sólo cosas de personas en las que confíe, o si sabes lo que estás haciendo.", "gui_download_upload_progress_complete": "%p%, {0:s} transcurrido.", "gui_download_upload_progress_starting": "{0:s}, %p% (calculando)", "gui_download_upload_progress_eta": "{0:s}, tiempo restante estimado: {1:s}, %p%", @@ -161,8 +161,8 @@ "history_completed_tooltip": "{} completado", "error_cannot_create_downloads_dir": "No se ha podido crear la carpeta en modo de recepción: {}", "error_downloads_dir_not_writable": "La carpeta del modo de recepción está protegida contra escritura: {}", - "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de su ordenador si usted los abre. Sólo abra cosas de personas en las que confíe, o si sabe lo que está haciendo.", - "receive_mode_upload_starting": "La carga del tamaño total {} está comenzando", + "gui_receive_mode_warning": "El modo de recepción permite a la gente subir archivos a su ordenador.

    Algunos archivos pueden potencialmente tomar el control de tu ordenador si los abres. Abre sólo cosas de personas en las que confíes, o si sabes lo que estás haciendo.", + "receive_mode_upload_starting": "La subida con un tamaño total de {} está comenzando ahora", "receive_mode_received_file": "Recibido: {}", "gui_mode_share_button": "Compartir archivos", "gui_mode_receive_button": "Recibir archivos", @@ -184,7 +184,7 @@ "gui_download_in_progress": "Descarga iniciada {}", "gui_open_folder_error_nautilus": "No se puede abrir la carpeta porque nautilus no está disponible. El archivo está aquí: {}", "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicie OnionShare para que el cambio de idioma surta efecto.", + "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", "timeout_upload_still_running": "Esperando a que se complete la subida" } diff --git a/share/locale/fa.json b/share/locale/fa.json index 9b15a20d..d45ea27c 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "لغو شده", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", diff --git a/share/locale/id.json b/share/locale/id.json index 24da50a0..0557aca9 100644 --- a/share/locale/id.json +++ b/share/locale/id.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "Dibatalkan", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", diff --git a/share/locale/it.json b/share/locale/it.json index a6f6d265..8d4f5bf3 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -17,7 +17,7 @@ "gui_share_start_server": "Inizia la condivisione", "gui_share_stop_server": "Ferma la condivisione", "gui_copy_url": "Copia l' URL", - "gui_downloads": "Cronologia Dei Download", + "gui_downloads": "Cronologia Dei Downloads", "gui_canceled": "Cancellati", "gui_copied_url": "URL Copiato nella clipboard", "gui_please_wait": "Attendere prego...", diff --git a/share/locale/nl.json b/share/locale/nl.json index e8910d06..3ecc763b 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -23,7 +23,7 @@ "help_stay_open": "Blijven delen na afronden van eerste download", "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", - "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", diff --git a/share/locale/no.json b/share/locale/no.json index 4d474cea..381b4e84 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,6 +1,6 @@ { "give_this_url": "Gi denne adressen til mottakeren:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren.", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", diff --git a/share/locale/pl.json b/share/locale/pl.json index c45af162..d24a0818 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -164,7 +164,7 @@ "gui_mode_receive_button": "", "gui_settings_receiving_label": "", "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "Wybierz...", + "gui_settings_downloads_button": "Przeglądaj", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", "gui_settings_public_mode_checkbox": "", "systray_close_server_title": "", diff --git a/share/locale/ro.json b/share/locale/ro.json index b46f0b5d..54750c24 100644 --- a/share/locale/ro.json +++ b/share/locale/ro.json @@ -47,7 +47,7 @@ "gui_copy_hidservauth": "", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "Canceled", + "gui_canceled": "Anulat", "gui_copied_url_title": "", "gui_copied_url": "", "gui_copied_hidservauth_title": "", -- cgit v1.2.3-54-g00ecf From f329614ea4fc20dbfdb7bb536e19a18ba7fe531f Mon Sep 17 00:00:00 2001 From: MA Date: Fri, 28 Dec 2018 17:40:49 +0000 Subject: Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ --- share/locale/bg.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/share/locale/bg.json b/share/locale/bg.json index 660effe2..e17b2a88 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -1,19 +1,19 @@ { "config_onion_service": "Създаване на onion служба на порт {0:d}.", "preparing_files": "Архивира документи.", - "give_this_url": "Дай този адрес на получателя:", - "give_this_url_stealth": "Дай този адрес и HidServAuth реда на получателя:", - "give_this_url_receive": "Дай този адрес на подателя:", - "give_this_url_receive_stealth": "Дай този адрес и HidServAuth на подателя:", + "give_this_url": "Дайте този адрес на получателя:", + "give_this_url_stealth": "Дайте този адрес и HidServAuth реда на получателя:", + "give_this_url_receive": "Дайте този адрес на подателя:", + "give_this_url_receive_stealth": "Дайте този адрес и HidServAuth на подателя:", "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", - "not_a_file": "{10: s) не е валиден документ.", - "not_a_readable_file": "{0: s) не е четаем файл.", + "not_a_file": "{0: s) не е валиден документ.", + "not_a_readable_file": "{0:s) не е четаем файл.", "no_available_port": "Свободен порт не бе намерен, за да може onion service да бъде стартиран.", "other_page_loaded": "Адресът е зареден", "close_on_timeout": "Спряно, защото таймерът с автоматично спиране приключи", "closing_automatically": "Спряно, защото свалянето приключи", "timeout_download_still_running": "Изчакване на свалянето да приключи", - "timeout_upload_still_running": "Изчакаване ъплоудът да приключи", + "timeout_upload_still_running": "Изчакване ъплоудът да приключи", "large_filesize": "Предупреждение: изпращане на голям дял може да отнеме часове", "systray_menu_exit": "Изход", "systray_download_started_title": "OnionShare сваляне започна", -- cgit v1.2.3-54-g00ecf From 57c0d7b3f44aff4bc74e19160e315d2496c680bb Mon Sep 17 00:00:00 2001 From: MA Date: Fri, 28 Dec 2018 17:43:27 +0000 Subject: Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ --- share/locale/bg.json | 104 +++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/share/locale/bg.json b/share/locale/bg.json index e17b2a88..e8a8f3e5 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -1,5 +1,5 @@ { - "config_onion_service": "Създаване на onion служба на порт {0:d}.", + "config_onion_service": "Създаване на onion услуга на порт {0:d}.", "preparing_files": "Архивира документи.", "give_this_url": "Дайте този адрес на получателя:", "give_this_url_stealth": "Дайте този адрес и HidServAuth реда на получателя:", @@ -8,9 +8,9 @@ "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", "not_a_file": "{0: s) не е валиден документ.", "not_a_readable_file": "{0:s) не е четаем файл.", - "no_available_port": "Свободен порт не бе намерен, за да може onion service да бъде стартиран.", + "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана.", "other_page_loaded": "Адресът е зареден", - "close_on_timeout": "Спряно, защото таймерът с автоматично спиране приключи", + "close_on_timeout": "Спряно, защото автоматично спиращият таймер приключи", "closing_automatically": "Спряно, защото свалянето приключи", "timeout_download_still_running": "Изчакване на свалянето да приключи", "timeout_upload_still_running": "Изчакване ъплоудът да приключи", @@ -33,43 +33,43 @@ "help_filename": "Списък на документи или папки за споделяне", "help_config": "Персонализирано местоположение на JSON конфигурационен файл (незадължително)", "gui_drag_and_drop": "Плъзнете и пуснете файлове и папки, \nза да започнете споделяне", - "gui_add": "Добави", + "gui_add": "Добавете", "gui_delete": "Изтриване", - "gui_choose_items": "Избери", - "gui_share_start_server": "Започни споделянето", - "gui_share_stop_server": "Спри споделянето", - "gui_share_stop_server_shutdown_timeout": "Спри споделянето ({} остават)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Tаймерът с автоматично спиране терминира в {}", - "gui_receive_start_server": "Стартирай получаващ режим", - "gui_receive_stop_server": "Спри получаващия режим", - "gui_receive_stop_server_shutdown_timeout": "Спри получаващия режим ({} остават)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "Автоматично спиращияt таймер спира в {}", - "gui_copy_url": "Копирай адрес", - "gui_copy_hidservauth": "Копирай HidServAuth", - "gui_downloads": "Свали история", + "gui_choose_items": "Изберете", + "gui_share_start_server": "Започнете споделянето", + "gui_share_stop_server": "Спрете споделянето", + "gui_share_stop_server_shutdown_timeout": "Спрете споделянето ({} остават)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Автоматично спиращият таймерът терминира в {}", + "gui_receive_start_server": "Стартирайте получаващ режим", + "gui_receive_stop_server": "Спрете получаващия режим", + "gui_receive_stop_server_shutdown_timeout": "Спрете получаващия режим ({} остават)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Автоматично спиращият таймер спира в {}", + "gui_copy_url": "Копирайте адрес", + "gui_copy_hidservauth": "Копирайте HidServAuth", + "gui_downloads": "Свалете история", "gui_no_downloads": "Още няма изтегляния", "gui_canceled": "Отменен", - "gui_copied_url_title": "OnionShare адрес е копиран", + "gui_copied_url_title": "OnionShare адресът е копиран", "gui_copied_url": "OnionShare адресът е копиран към клипборда", "gui_copied_hidservauth_title": "HidServAuth е копиран", - "gui_copied_hidservauth": "HidServAuth ред е копиран към клипборда", - "gui_please_wait": "Започва... - кликни за отменяне", - "gui_download_upload_progress_complete": "% p% (0: s) изтече.", - "gui_download_upload_progress_starting": "{10: s),% p% (изчисляване)", - "gui_download_upload_progress_eta": "{0: s), eta: {1: s),% p%", + "gui_copied_hidservauth": "HidServAuth редът е копиран към клипборда", + "gui_please_wait": "Започва... кликни за отменяне.", + "gui_download_upload_progress_complete": "%p%, {0:s} изтече.", + "gui_download_upload_progress_starting": "{0:s}, %p% (изчисляване)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Не толкова бързо", "gui_share_quit_warning": "Намирате се в процес на изпращане на данни. Сигурни ли сте, че искате да спрете OnionShare?", "gui_receive_quit_warning": "Намирате се в процес на получаване на файлове. Сигурни ли сте, че искате да спрете OnionShare?", "gui_quit_warning_quit": "Изход", "gui_quit_warning_dont_quit": "Отказ", - "error_rate_limit": "Някой е направил прекалено много грешни опити за адреса ти, което означава, че може да се опитват да го отгатнат, така че OnionShare спря сървъра. Стартирайте споделянето отново и изпратете нов адрес на получателя за споделяне.", - "zip_progress_bar_format": "Компресира:% p%", + "error_rate_limit": "Някой е направил прекалено много грешни опити за адреса Ви, което означава, че може да се опитват да го отгатнат, така че OnionShare спря сървъра. Стартирайте споделянето отново и изпратете нов адрес на получателя за споделяне.", + "zip_progress_bar_format": "Компресира: %p%", "error_stealth_not_supported": "За да използвате ауторизация на клиента Ви трябва поне Tor 0.2.9.1-alpha (или на браузъра 6.5) и python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare изисква поне Tor 0.2.7.1 и python3-stem 1.4.0.", "gui_settings_window_title": "Настройки", "gui_settings_whats_this": "Какво е това?", - "gui_settings_stealth_option": "Използвай клиент ауторизация (наследствен)", + "gui_settings_stealth_option": "Използвайте клиент ауторизация (наследствен)", "gui_settings_stealth_hidservauth_string": "След като Вашия частен ключ бе запазен за повторна употреба, можете сега да кликнете, за да копирате Вашия HidServAuth.", "gui_settings_autoupdate_label": "Провери за нова версия", "gui_settings_autoupdate_option": "Уведоми ме, когато е налице нова версия", @@ -79,16 +79,16 @@ "gui_settings_general_label": "Общи настройки", "gui_settings_sharing_label": "Настройки на споделяне", "gui_settings_close_after_first_download_option": "Спри споделянето след първото изтегляне", - "gui_settings_connection_type_label": "Как OnionShare да се свързже с Тор?", + "gui_settings_connection_type_label": "Как OnionShare да се свържe с Тор?", "gui_settings_connection_type_bundled_option": "Използвай Тор версия, вградена в OnionShare", "gui_settings_connection_type_automatic_option": "Опит за автоматична конфигурация с Тор браузъра", - "gui_settings_connection_type_control_port_option": "Свържете използвайки контролен порт", + "gui_settings_connection_type_control_port_option": "Свържете, използвайки контролен порт", "gui_settings_connection_type_socket_file_option": "Свържете се използвайки сокет", "gui_settings_connection_type_test_button": "Тест на връзката с Тор", "gui_settings_control_port_label": "Контролен порт", "gui_settings_socket_file_label": "Сокет файл", "gui_settings_socks_label": "SOCKS порт", - "gui_settings_authenticate_label": "Настройки на Tor за удостоверяване на автентичността", + "gui_settings_authenticate_label": "Настройки на Тор за удостоверяване на автентичността", "gui_settings_authenticate_no_auth_option": "Без автентикация или cookie автентикация", "gui_settings_authenticate_password_option": "Парола", "gui_settings_password_label": "Парола", @@ -97,10 +97,10 @@ "gui_settings_tor_bridges_obfs4_radio_option": "Използвайте вградените obfs4 pluggable транспорти", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Използвайте вградените obfs4 pluggable транспорти (изисква obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Използвайте вградените meek_lite (Azure) pluggable транспорти", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Използвайте вградените meek_lite (Azure) pluggable транспорти (изисква obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Предупреждение: meek_lite мостовета са много скъпи за проекта Тор.

    Използвайте ги само, ако не могат да се свържат пряко чрез obfs4 транспорти или други нормални мостове с Тор.", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Използвайте вградените meek_lite (Azure) pluggable транспорти (изискват obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Предупреждение: meek_lite мостовета са много скъпи за проекта Тор.

    Използвайте ги само, ако не можете да се свържете пряко чрез obfs4 транспорти или други нормални мостове с Тор.", "gui_settings_tor_bridges_custom_radio_option": "Използвайте персонализирани мостове", - "gui_settings_tor_bridges_custom_label": "Може намерите мостове на https://bridges.torproject.org", + "gui_settings_tor_bridges_custom_label": "Може да намерите мостове на https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "Нито един от добавените от Вас мостове работят.\nПроверете ги отново или добавете други.", "gui_settings_button_save": "Запазване", "gui_settings_button_cancel": "Отказ", @@ -110,21 +110,21 @@ "settings_error_unknown": "Не мога да се свържа с Тор контролера, защото Вашите настройки не правят смисъл.", "settings_error_automatic": "Не мога да се свържа с Тор контролера. Стартиран ли е Тор браузерът във фонов режим (достъпен от torproject. org)?", "settings_error_socket_port": "Не мога да се свържа с Тор контролера в {}:{}.", - "settings_error_socket_file": "Не мога да се свържа с Тор контролера, използвайки сокет файл .{}.", + "settings_error_socket_file": "Не мога да се свържа с Тор контролера, използвайки сокет файл {}.", "settings_error_auth": "Свързан с {}:{}, но не може да се идентифицира. Може би това не е Тор контролер?", "settings_error_missing_password": "Свързан с Тор контролер, но той изисква парола за идентификация.", - "settings_error_unreadable_cookie_file": "Свързан с Тор контролер, но паролата може да е грешна, или на Вашият потребител не е позволено да чете бисквитката файл.", + "settings_error_unreadable_cookie_file": "Свързан с Тор контролер, но паролата може да е грешна, или на Вашият потребител да не е позволено да чете бисквитката файл.", "settings_error_bundled_tor_not_supported": "Използване на Тор версия, идваща с OnionShare не работи в режим на разработчик под Windows или macOS.", - "settings_error_bundled_tor_timeout": "Oтнема прекалено дълго време да се свържа с Тор. Може би не сте свързани с интернет или системния часовник е неточен?", + "settings_error_bundled_tor_timeout": "Oтнема прекалено дълго време да се свържа с Тор. Може би не сте свързани с интернет или системният часовник е неточен?", "settings_error_bundled_tor_broken": "OnionShare не можа да се свърже с Тор във фонов режим:\n{}", "settings_test_success": "Свързан с Тор контролер.\n\nТор версия: {}\nПоддържа ephemeral onion services: {}\nПоддържа клиент автентикация: {}\nПоддържа следваща генерация .onion адреси: {}", "error_tor_protocol_error": "Станала е грешка с Тор: {}", - "error_tor_protocol_error_unknown": "Имаше неизвестен грешка с Тор", + "error_tor_protocol_error_unknown": "Имаше неизвестена грешка с Тор", "error_invalid_private_key": "Този тип частен ключ е неподдържан", "connecting_to_tor": "Свързване към Тор мрежата", "update_available": "Има нов OnionShare. Кликнете тук, за да го изтеглите.

    Вие използвате {}, а последният е {}.", - "update_error_check_error": "Не може да се провери за нови версии: OnionShare сайтът казва, че не разпознава последната версия '{}'…", - "update_error_invalid_latest_version": "Не мОГ да проверя за нова версия: Може би не сте свързани към Тор, или OnionShare уебсайтът е изключен?", + "update_error_check_error": "Не мога да проверя за нови версии: OnionShare сайтът казва, че не разпознава последната версия '{}'…", + "update_error_invalid_latest_version": "Не мога да проверя за нова версия: Може би не сте свързани към Тор или OnionShare уебсайтът е изключен?", "update_not_available": "Вие изпозвате псоледната версия на OnionShare.", "gui_tor_connection_ask": "Отворете настройките, за да възстановите връзката с Тор?", "gui_tor_connection_ask_open_settings": "Да", @@ -132,17 +132,17 @@ "gui_tor_connection_error_settings": "Опитайте се да промените в настройките как OnionShare се свързва с Тор.", "gui_tor_connection_canceled": "Не може да се установи връзка с Тор.\n\nУверете се, че имате връзка с интернтет, след което отново отворете OnionShare и пренастройте връзката с Тор.", "gui_tor_connection_lost": "Връзката с Тор е прекъсната.", - "gui_server_started_after_timeout": "Аввтоматично спиращият таъмер спря преди сървърът да стартира.\nМоля направете нов дял.", + "gui_server_started_after_timeout": "Автоматично спиращият таймер спря преди сървърът да стартира.\nМоля направете нов дял.", "gui_server_timeout_expired": "Автоматично спиращият таймер спря.\nМоля актуализирайте за да започнете споделяне.", "share_via_onionshare": "Споделете го чрез OnionShare", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", + "gui_use_legacy_v2_onions_checkbox": "Използвайте стари адреси", + "gui_save_private_key_checkbox": "Използвайте постоянни адреси (стари)", "gui_share_url_description": "Всеки с този OnionShare адрес може да свали Вашите файлове използвайки Тор браузера: ", "gui_receive_url_description": "Всеки с този OnionShare адрес може да качи файлове на Вашия компютър, използвайки Тор браузера: ", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "Тази част няма да спре автоматично.", - "gui_url_label_onetime": "Тази част ще спре след първото изпълнение.", - "gui_url_label_onetime_and_persistent": "", + "gui_url_label_persistent": "Този дял няма да спре автоматично.

    Всеки следващ дял ще използва повторно адреса. (За да използвате еднократни адреси, изключете \"Използвайте постоянен адрес\" в настройките)", + "gui_url_label_stay_open": "Този дял няма да спре автоматично.", + "gui_url_label_onetime": "Този дял ще спре след първото изпълнение.", + "gui_url_label_onetime_and_persistent": "Този дял няма да спре автоматично.

    Всеки следващ дял ще използва повторно адреса. (За да използвате еднократни адреси, изключете \"Използвайте постоянен адрес\" в настройките)", "gui_status_indicator_share_stopped": "Готово за споделяне", "gui_status_indicator_share_working": "Започва…", "gui_status_indicator_share_started": "Споделяне", @@ -157,12 +157,12 @@ "info_completed_uploads_tooltip": "{} ъплоудът(ите) е(са) завършен(и)", "error_cannot_create_downloads_dir": "Не мога да създам папка за режим на приемане: {}", "receive_mode_downloads_dir": "Документи, изпратени до Вас, се появяват в тази папка: {}", - "receive_mode_warning": "Предупреждение: Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър. Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", - "gui_receive_mode_warning": "Режим на приемане позволява на хора да ъплоудват файлове на Вашия компютър.

    Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "receive_mode_warning": "Предупреждение: Режим на приемане позволява на хора да качват файлове на Вашия компютър. Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", + "gui_receive_mode_warning": "Режим на приемане позволява на хора да качват файлове на Вашия компютър.

    Някои файлове могат потенциално да поемат контрол над компютъра Ви, ако ги отворите. Отваряйте единствено неща от хора, на които вярвате или ако знаете какво правите.", "receive_mode_upload_starting": "Ъплоуд на общ размер {} започва", "receive_mode_received_file": "Получено: {}", "gui_mode_share_button": "Споделяне на файлове", - "gui_mode_receive_button": "Получи документи", + "gui_mode_receive_button": "Получете документи", "gui_settings_receiving_label": "Настройки на получаване", "gui_settings_downloads_label": "Запазете файлове в", "gui_settings_downloads_button": "Разглеждане", @@ -171,15 +171,15 @@ "systray_close_server_message": "Един ползвател затвори сървъра", "systray_page_loaded_title": "OnionShare страницата е заредена", "systray_download_page_loaded_message": "Един ползвател зареди свалената страница", - "systray_upload_page_loaded_message": "Един ползвател зареди качената страница", + "systray_upload_page_loaded_message": "Един ползвател зареди ъплоуд страницата", "gui_uploads": "Ъплоуд история", - "gui_no_uploads": "Все още няма ъплоуди", + "gui_no_uploads": "Все още няма качвания", "gui_clear_history": "Изтрий всичко", - "gui_upload_in_progress": "Ъплоудът започна", + "gui_upload_in_progress": "Качването започна", "gui_upload_finished_range": "Качен {} на {}", "gui_upload_finished": "Качен {}", "gui_download_in_progress": "Изтеглянето започна {}", - "gui_open_folder_error_nautilus": "Не може да бъде отворена папка, защото \"nautilus\" не е на разположение. Файлът е тук: {}", + "gui_open_folder_error_nautilus": "Не мога да отворя папка, защото \"nautilus\" не е на разположение. Файлът е тук: {}", "gui_settings_language_label": "Предпочитан език", - "gui_settings_language_changed_notice": "Рестартирайте OnionShare Вашата промяна на език да има ефект." + "gui_settings_language_changed_notice": "За да влезе промяна Ви на език в сила, рестартирайте OnionShare." } -- cgit v1.2.3-54-g00ecf From bcefae9212bbaaf2d22344968c8b39eb5ff25fc9 Mon Sep 17 00:00:00 2001 From: Jonatan Nyberg Date: Sat, 29 Dec 2018 03:29:18 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 267 ++++++++++++++++++++++++++------------------------- 1 file changed, 135 insertions(+), 132 deletions(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index c5f6e8da..c0e28ec7 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -10,7 +10,7 @@ "not_a_readable_file": "{0:s} är inte en läsbar fil.", "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", "other_page_loaded": "Adress laddad", - "close_on_timeout": "", + "close_on_timeout": "Stoppad för att automatiska stopp-timern tiden tog slut", "closing_automatically": "Stannade för att nedladdningen blev klar", "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", @@ -39,7 +39,7 @@ "gui_share_start_server": "Påbörja delning", "gui_share_stop_server": "Avbryt delning", "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", - "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern slutar vid {}", "gui_receive_start_server": "Starta Mottagarläge", "gui_receive_stop_server": "Avsluta Mottagarläge", "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", @@ -52,134 +52,137 @@ "gui_copied_url_title": "OnionShare Adress Kopierad", "gui_copied_url": "OnionShare adress kopierad till urklipp", "gui_copied_hidservauth_title": "HidServAuth Kopierad", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_copied_hidservauth": "HidServAuth-rad kopierad till urklipp", + "gui_please_wait": "Börjar... klicka för att avbryta.", + "gui_download_upload_progress_complete": "%p%, {0:s} förflutit.", + "gui_download_upload_progress_starting": "{0:s}, %p% (beräknar)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Inte så fort", + "gui_share_quit_warning": "Du håller på att skicka filer. Är du säker på att du vill avsluta OnionShare?", + "gui_receive_quit_warning": "Du håller på att ta emot filer. Är du säker på att du vill avsluta OnionShare?", + "gui_quit_warning_quit": "Avsluta", + "gui_quit_warning_dont_quit": "Avbryt", + "error_rate_limit": "Någon har gjort för många felaktiga försök på din adress, vilket innebär att de kan försöka gissa det, så OnionShare har stoppat servern. Börja dela igen och skicka mottagaren en ny adress att dela.", + "zip_progress_bar_format": "Komprimerar: %p%", + "error_stealth_not_supported": "För att använda klientauktorisering behöver du minst både Tor 0.2.9.1-alpha (eller Tor Browser 6.5) och python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare kräver minst både Tor 0.2.7.1 och python3-stem 1.4.0.", + "gui_settings_window_title": "Inställningar", + "gui_settings_whats_this": "Vad är det här?", + "gui_settings_stealth_option": "Använd klientauktorisering", + "gui_settings_stealth_hidservauth_string": "Efter att ha sparat din privata nyckel för återanvändning, innebär att du kan nu\nklicka för att kopiera din HidServAuth.", + "gui_settings_autoupdate_label": "Sök efter ny version", + "gui_settings_autoupdate_option": "Meddela mig när en ny version är tillgänglig", + "gui_settings_autoupdate_timestamp": "Senast kontrollerad: {}", + "gui_settings_autoupdate_timestamp_never": "Aldrig", + "gui_settings_autoupdate_check_button": "Sök efter ny version", + "gui_settings_general_label": "Allmänna inställningar", + "gui_settings_sharing_label": "Delningsinställningar", + "gui_settings_close_after_first_download_option": "Sluta dela efter första hämtningen", + "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", + "gui_settings_connection_type_bundled_option": "Använd Tor-versionen inbyggd i OnionShare", + "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", + "gui_settings_connection_type_control_port_option": "Anslut med kontrollport", + "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", + "gui_settings_connection_type_test_button": "Provningsanslutning till Tor", + "gui_settings_control_port_label": "Kontrollport", + "gui_settings_socket_file_label": "Socket-fil", + "gui_settings_socks_label": "SOCKS-port", + "gui_settings_authenticate_label": "Tor-autentiseringsinställningar", + "gui_settings_authenticate_no_auth_option": "Ingen autentisering eller kak-autentisering", + "gui_settings_authenticate_password_option": "Lösenord", + "gui_settings_password_label": "Lösenord", + "gui_settings_tor_bridges": "Stöd för Tor broar", + "gui_settings_tor_bridges_no_bridges_radio_option": "Använd inte broar", + "gui_settings_tor_bridges_obfs4_radio_option": "Använd inbyggda obfs4 pluggbara transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Använd inbyggda obfs4 pluggbara transporter (kräver obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Använd inbyggda meek_lite (Azure) pluggbara transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Använd inbyggda meek_lite (Azure) pluggbara transporter (kräver obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Varning: meek_lite-broarna är mycket kostsamma för att Tor-projektet ska kunna köras.

    Använd dem endast om det inte går att ansluta till Tor direkt, via obfs4-transporter eller andra normala broar.", + "gui_settings_tor_bridges_custom_radio_option": "Använd anpassade broar", + "gui_settings_tor_bridges_custom_label": "Du kan få broar från https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ingen av broarna du lagt till arbete.\nDubbelkolla dem eller lägga till andra.", + "gui_settings_button_save": "Spara", + "gui_settings_button_cancel": "Avbryt", + "gui_settings_button_help": "Hjälp", + "gui_settings_shutdown_timeout_checkbox": "Använd automatiska stopp-timern", + "gui_settings_shutdown_timeout": "Stoppa delningen vid:", + "settings_error_unknown": "Kan inte ansluta till Tor-regulatorn eftersom dina inställningar inte är vettiga.", + "settings_error_automatic": "Kunde inte ansluta till Tor-regulatorn. Körs Tor Browser (tillgänglig från torproject.org) i bakgrunden?", + "settings_error_socket_port": "Det går inte att ansluta till Tor-regulatorn på {}:{}.", + "settings_error_socket_file": "Det går inte att ansluta till Tor-regulatorn med socket-filen {}.", + "settings_error_auth": "Ansluten till {}:{}, men kan inte autentisera. Kanske är det här inte en Tor-regulator?", + "settings_error_missing_password": "Ansluten till Tor-regulatorn, men den kräver ett lösenord för att autentisera.", + "settings_error_unreadable_cookie_file": "Ansluten till Tor-regulatorn, men lösenordet kan vara fel, eller din användare är inte tillåtet att läsa kakfilen.", + "settings_error_bundled_tor_not_supported": "Användning av Tor-versionen som följer med OnionShare fungerar inte i utvecklarläge på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "Det tar för lång tid att ansluta till Tor. Kanske är du inte ansluten till Internet, eller har en felaktig systemklocka?", + "settings_error_bundled_tor_broken": "OnionShare kunde inte ansluta till Tor i bakgrunden:\n{}", + "settings_test_success": "Ansluten till Tor-regulatorn.\n\nTor version: {}\nStöder efemära onion-tjänster: {}.\nStöder klientautentisering: {}.\nStöder nästa generations .onion-adresser: {}.", + "error_tor_protocol_error": "Det fanns ett fel med Tor: {}", + "error_tor_protocol_error_unknown": "Det fanns ett okänt fel med Tor", + "error_invalid_private_key": "Denna privata nyckeltyp stöds inte", + "connecting_to_tor": "Ansluter till Tor-nätverket", + "update_available": "Ny OnionShare utgiven. Klicka här för att få det.

    Du använder {} och det senaste är {}.", + "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", + "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: kanske är du inte ansluten till Tor, eller OnionShare-webbplatsen är nere?", + "update_not_available": "Du kör den senaste OnionShare.", + "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_ask_quit": "Avsluta", + "gui_tor_connection_error_settings": "Försök ändra hur OnionShare ansluter till Tor-nätverket i inställningarna.", + "gui_tor_connection_canceled": "Kunde inte ansluta till Tor.\n\nSe till att du är ansluten till Internet, öppna sedan OnionShare och ställ in anslutningen till Tor.", + "gui_tor_connection_lost": "Frånkopplad från Tor.", + "gui_server_started_after_timeout": "Automatiska stopp-timern tog slut innan servern startade.\nVänligen gör en ny delning.", + "gui_server_timeout_expired": "Automatiska stopp-timern har redan slutat.\nUppdatera den för att börja dela.", + "share_via_onionshare": "OnionShare den", + "gui_use_legacy_v2_onions_checkbox": "Använd äldre adresser", + "gui_save_private_key_checkbox": "Använd en beständig adress", + "gui_share_url_description": "Alla med denna OnionShare-adress kan hämta dina filer med hjälp av Tor Browser: ", + "gui_receive_url_description": "Alla med denna OnionShare-adress kan skicka filer till din dator med hjälp av Tor Browser: ", + "gui_url_label_persistent": "Denna delning kommer inte automatiskt att sluta.
    < br>Varje efterföljande delning återanvänder adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)", + "gui_url_label_stay_open": "Denna delning kommer inte automatiskt att sluta.", + "gui_url_label_onetime": "Denna delning kommer att sluta efter första slutförandet.", + "gui_url_label_onetime_and_persistent": "Denna delning kommer inte automatiskt att sluta.
    < br>Varje efterföljande delning kommer att återanvända adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)", + "gui_status_indicator_share_stopped": "Redo att dela", + "gui_status_indicator_share_working": "Börjar…", + "gui_status_indicator_share_started": "Delar", + "gui_status_indicator_receive_stopped": "Redo att ta emot", + "gui_status_indicator_receive_working": "Börjar…", + "gui_status_indicator_receive_started": "Tar emot", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "history_in_progress_tooltip": "{} pågår", + "history_completed_tooltip": "{} slutförda", + "info_in_progress_uploads_tooltip": "{} pågående sändning(ar)", + "info_completed_uploads_tooltip": "{} sändning(ar) slutförd(a)", + "error_cannot_create_downloads_dir": "Det gick inte att skapa mappen mottagningsläge: {}", + "receive_mode_downloads_dir": "Filer som skickas till dig visas i den här mappen: {}", + "receive_mode_warning": "Varning: Mottagningsläge låter personer skicka filer till din dator. Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", + "gui_receive_mode_warning": "Mottagningsläge låter personer skicka filer till din dator.

    Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", + "receive_mode_upload_starting": "Sändning av total storlek {} börjar", + "receive_mode_received_file": "Mottaget: {}", + "gui_mode_share_button": "Dela filer", + "gui_mode_receive_button": "Ta emot filer", + "gui_settings_receiving_label": "Mottagning-inställningar", + "gui_settings_downloads_label": "Spara filer till", + "gui_settings_downloads_button": "Bläddra", + "gui_settings_public_mode_checkbox": "Offentligt läge", + "systray_close_server_title": "OnionShare-servern stängd", + "systray_close_server_message": "En användare stängde servern", + "systray_page_loaded_title": "OnionShare-sidan lästes in", + "systray_download_page_loaded_message": "En användare läste in hämtningssidan", + "systray_upload_page_loaded_message": "En användare läste in sändningssidan", + "gui_uploads": "Sändningshistoriken", + "gui_no_uploads": "Inga sändningar ännu", + "gui_clear_history": "Rensa alla", + "gui_upload_in_progress": "Sändning påbörjad {}", + "gui_upload_finished_range": "Skickade {} till {}", + "gui_upload_finished": "Skickade {}", + "gui_download_in_progress": "Hämtning påbörjad {}", + "gui_open_folder_error_nautilus": "Det går inte att öppna mappen eftersom nautilus inte är tillgänglig. Filen är här: {}", + "gui_settings_language_label": "Föredraget språk", + "gui_settings_language_changed_notice": "Starta om OnionShare för att din språkändring ska träda i kraft.", + "gui_add_files": "Lägg till filer", + "gui_add_folder": "Lägg till mapp", + "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar" } -- cgit v1.2.3-54-g00ecf From b0b3240a723903520557b141f1e23436bc7068ab Mon Sep 17 00:00:00 2001 From: leonardo Date: Sat, 29 Dec 2018 18:30:53 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index 8d4f5bf3..8bbc657f 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -83,5 +83,16 @@ "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", "gui_settings_language_label": "Lingua preferita", "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", - "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati" + "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", + "timeout_upload_still_running": "Aspettando che si completi l'upload", + "gui_add_files": "Aggiungi file", + "gui_add_folder": "Aggiungi cartella", + "gui_settings_connection_type_control_port_option": "Connettendo usano la porta di controllo", + "gui_settings_connection_type_socket_file_option": "Connessione usano file di socket", + "gui_settings_connection_type_test_button": "Testa la connessione Tor", + "gui_settings_socket_file_label": "File di socket", + "gui_settings_socks_label": "Porta SOCKS", + "gui_settings_authenticate_label": "Impostazioni autenticazione Tor", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password" } -- cgit v1.2.3-54-g00ecf From 3275bc9bec671afc11b50115cbcdd4d51b9c8dd2 Mon Sep 17 00:00:00 2001 From: monica calcagnini Date: Sun, 30 Dec 2018 15:44:09 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index 8bbc657f..2c357fcd 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,10 +1,10 @@ { - "preparing_files": "Preparazione dei files da condividere.", - "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", - "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", - "not_a_file": "{0:s} non è un file.", + "preparing_files": "Compressione dei file in corso.", + "give_this_url": "Dai questa URL alla persona al destinatario:", + "ctrlc_to_stop": "Premi Ctrl+C per fermare il server", + "not_a_file": "{0:s} non è un file valido.", "other_page_loaded": "URL caricato", - "closing_automatically": "Chiusura automatica dopo aver finito il download", + "closing_automatically": "Chiusura automatica, download completato", "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", "help_local_only": "Non usare tor: è solo per lo sviluppo", "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", @@ -22,13 +22,13 @@ "gui_copied_url": "URL Copiato nella clipboard", "gui_please_wait": "Attendere prego...", "zip_progress_bar_format": "Elaborazione files: %p%", - "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", - "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al recipiente:", + "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.", + "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al destinatario:", "give_this_url_receive": "Dai questo indirizzo al mittente:", - "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth:", - "not_a_readable_file": "{0:s} non e' un file leggibile.", - "no_available_port": "Non trovo nessuna porta disponibile per far partire un onion service", - "close_on_timeout": "tempo scaduto, chiudo", + "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth con il mittente:", + "not_a_readable_file": "{0:s} non è un file leggibile.", + "no_available_port": "Non è stato possibile trovare alcuna porta per avviare il servizio onion", + "close_on_timeout": "Arrestato per tempo scaduto", "timeout_download_still_running": "download in corso, attendere", "systray_menu_exit": "Fine", "systray_download_started_title": "Inizio il Download con OnionShare", -- cgit v1.2.3-54-g00ecf From ddc3dc5b8d710704f7c8984dfe6229de8b630530 Mon Sep 17 00:00:00 2001 From: monica calcagnini Date: Sun, 30 Dec 2018 15:50:28 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 200 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 145 insertions(+), 55 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index 2c357fcd..ba8b280a 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -5,23 +5,23 @@ "not_a_file": "{0:s} non è un file valido.", "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica, download completato", - "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare tor: è solo per lo sviluppo", - "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", + "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore", + "help_local_only": "Non usare Tor: versione in fase di sviluppo", + "help_stay_open": "Mantieni il servizio avviato anche dopo aver completato il primo download", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", - "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", + "gui_drag_and_drop": "Trascina e rilascia i file e le cartelle per iniziare la condivisione", "gui_add": "Aggiungi", "gui_delete": "Cancella", "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", - "gui_share_stop_server": "Ferma la condivisione", - "gui_copy_url": "Copia l' URL", - "gui_downloads": "Cronologia Dei Downloads", - "gui_canceled": "Cancellati", - "gui_copied_url": "URL Copiato nella clipboard", - "gui_please_wait": "Attendere prego...", - "zip_progress_bar_format": "Elaborazione files: %p%", + "gui_share_stop_server": "Arresta la condivisione", + "gui_copy_url": "Copia la URL", + "gui_downloads": "Cronologia dei Download", + "gui_canceled": "Annullato", + "gui_copied_url": "URL Copiato negli appunti", + "gui_please_wait": "Avviato... Cliccare per interrompere.", + "zip_progress_bar_format": "Compressione in corso: %p%", "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.", "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al destinatario:", "give_this_url_receive": "Dai questo indirizzo al mittente:", @@ -30,69 +30,159 @@ "no_available_port": "Non è stato possibile trovare alcuna porta per avviare il servizio onion", "close_on_timeout": "Arrestato per tempo scaduto", "timeout_download_still_running": "download in corso, attendere", - "systray_menu_exit": "Fine", - "systray_download_started_title": "Inizio il Download con OnionShare", - "systray_download_started_message": "Una persona ha iniziato il download dei tuoi file", + "systray_menu_exit": "Termina", + "systray_download_started_title": "Download con OnionShare avviato", + "systray_download_started_message": "Un utente ha iniziato il download dei tuoi file", "systray_download_completed_title": "Download completato", - "systray_download_completed_message": "il download dei tuoi file è stato completato", + "systray_download_completed_message": "L'utente ha terminato il download dei tuoi file", "systray_download_canceled_title": "Download cancellato", - "systray_download_canceled_message": "la persona ha interrotto il download", - "systray_upload_started_title": "Iniziando upload", - "systray_upload_started_message": "Iniziando upload di file sul tuo computer", + "systray_download_canceled_message": "l'utente ha interrotto il download", + "systray_upload_started_title": "Upload con OnionShare avviato", + "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer", "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", - "help_stealth": "Richiedi autorizzazione (avanzato)", - "help_config": "Specifica il path del file JSON personalizzato", - "gui_share_stop_server_shutdown_timeout": "Ferma la condivisione ({}s rimanenti)", - "gui_share_stop_server_shutdown_timeout_tooltip": "timer termina in {}", - "gui_receive_start_server": "Inizia modalitá ricezione", - "gui_receive_stop_server": "Termina modalitá ricezione", - "gui_receive_stop_server_shutdown_timeout": "Interrompi modalitá ricezione ({}s rimanenti)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "timer termina in {}", - "gui_copy_hidservauth": "Copia URL segreto", - "gui_no_downloads": "ancora niente", + "help_stealth": "Usa l'autorizzazione del client (avanzato)", + "help_config": "Specifica il percorso del file di configurazione del JSON personalizzato", + "gui_share_stop_server_shutdown_timeout": "Arresta la condivisione ({}s rimanenti)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Il timer si arresterà tra {}", + "gui_receive_start_server": "Inizia la ricezione", + "gui_receive_stop_server": "Arresta la ricezione", + "gui_receive_stop_server_shutdown_timeout": "Interrompi la ricezione ({}s rimanenti)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Il timer termina tra {}", + "gui_copy_hidservauth": "Copia HidServAuth", + "gui_no_downloads": "Ancora nessun Download", "gui_copied_url_title": "Indirizzo OnionShare copiato", - "gui_copied_hidservauth_title": "URL segreto copiato", - "gui_copied_hidservauth": "URL segreto copiato", - "gui_download_upload_progress_complete": "%p%, {0:s} mancanti.", - "gui_download_upload_progress_starting": "{0:s}, %p% (calcolando)", + "gui_copied_hidservauth_title": "HidServAuth copiato", + "gui_copied_hidservauth": "HidServAuth copiato negli appunti", + "gui_download_upload_progress_complete": "%p%, {0:s} trascorsi.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calcolato)", "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org", - "gui_quit_title": "Non cosí in fretta", - "gui_share_quit_warning": "Stai per inviare dai file. Sei sicuro di voler uscire da OnionShare?", - "gui_receive_quit_warning": "Stai ricevendo file, vuoi davvero terminare e chiudere OnionShare?", + "gui_quit_title": "Non così in fretta", + "gui_share_quit_warning": "Stai per inviare dei file. Sei sicuro di voler uscire da OnionShare?", + "gui_receive_quit_warning": "Stai per ricevere dei file, vuoi davvero terminare e chiudere OnionShare?", "gui_quit_warning_quit": "Esci", "gui_quit_warning_dont_quit": "Cancella", - "error_rate_limit": "Qualcosa ha fatto troppi tentativi a questo indirizzo, questo potrebbe esporre la sicurezza dell'indirizzo, quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL che verrá generato.", - "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", + "error_rate_limit": "Qualcuno ha tentato troppe volte di accedere al tuo indirizzo, questo potrebbe comprometterne la sicurezza quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL.", + "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno della versione di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", - "gui_settings_window_title": "Preferenze", - "gui_settings_whats_this": "Cos'e' questo?", - "help_receive": "Ricevi condivisioni invece di inviarle", + "gui_settings_window_title": "Impostazioni", + "gui_settings_whats_this": "Cos'è questo?", + "help_receive": "Ricevi le condivisioni invece di inviarle", "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", - "gui_settings_stealth_hidservauth_string": "Dopo aver salvato la tua chiave privata per il riutilizzo, significa che ora puoi\nclicca per copiare il tuo HidServAuth.", - "gui_settings_autoupdate_label": "controlla la nuova versione", - "gui_settings_autoupdate_option": "Notificami quando una nuova versione è disponibile", - "gui_settings_autoupdate_timestamp": "ultimo controllo", + "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi\ncliccare per copiare il tuo HidServAuth.", + "gui_settings_autoupdate_label": "Controlla se vi sono nuove versioni", + "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione", + "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}", "gui_settings_autoupdate_timestamp_never": "Mai", - "gui_settings_autoupdate_check_button": "Controlla per una nuova nuova versione", + "gui_settings_autoupdate_check_button": "Controlla per una nuova versione", "gui_settings_general_label": "Impostazioni generali", - "gui_settings_sharing_label": "Impostazione Condivise", - "gui_settings_close_after_first_download_option": "Ferma la condivisione dopo il primo download", - "gui_settings_connection_type_label": "Come OnionShare si connette a tor?", + "gui_settings_sharing_label": "Sto condividendo le impostazioni", + "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo il primo download", + "gui_settings_connection_type_label": "Come si dovrebbe connettere OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", "gui_settings_language_label": "Lingua preferita", "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", - "timeout_upload_still_running": "Aspettando che si completi l'upload", + "timeout_upload_still_running": "In attesa del completamento dell'upload", "gui_add_files": "Aggiungi file", - "gui_add_folder": "Aggiungi cartella", - "gui_settings_connection_type_control_port_option": "Connettendo usano la porta di controllo", - "gui_settings_connection_type_socket_file_option": "Connessione usano file di socket", - "gui_settings_connection_type_test_button": "Testa la connessione Tor", + "gui_add_folder": "Aggiungi una cartella", + "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo", + "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket", + "gui_settings_connection_type_test_button": "Prova la connessione Tor", "gui_settings_socket_file_label": "File di socket", "gui_settings_socks_label": "Porta SOCKS", - "gui_settings_authenticate_label": "Impostazioni autenticazione Tor", + "gui_settings_authenticate_label": "Impostazioni di autenticazione Tor", "gui_settings_authenticate_password_option": "Password", - "gui_settings_password_label": "Password" + "gui_settings_password_label": "Password", + "gui_settings_control_port_label": "Porta di controllo", + "gui_settings_authenticate_no_auth_option": "Nessuna autenticazione o autenticazione tramite cookie", + "gui_settings_tor_bridges": "Supporto bridge Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Non usare i bridge", + "gui_settings_tor_bridges_obfs4_radio_option": "Usare i trasporti obfs4 integrati selezionabili", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usare i trasporti obfs4 integrati selezionabili (richiede obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usare i trasporti integrati meek_lite (Azure) selezionabili", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usare i trasporti integrati meek_lite (Azure) selezionabili (richiede obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Attenzione: i bridge meek_lite sono molto pesanti per l'esecuzione del progetto Tor.

    Da usare solo se impossibile connettersi a Tor direttamente, con obfs4, o altri bridge normali.", + "gui_settings_tor_bridges_custom_label": "Puoi prendere i bridge da 1 https://bridges.torproject.org2", + "gui_settings_tor_bridges_invalid": "Nessuno dei bridge che hai aggiunto funzionano\nControllali nuovamente o aggiungine altri.", + "gui_settings_button_save": "Salva", + "gui_settings_button_cancel": "Cancella", + "gui_settings_button_help": "Aiuto", + "gui_settings_shutdown_timeout_checkbox": "Utilizza il timer di arresto automatico", + "gui_settings_shutdown_timeout": "Ferma la condivisione alle:", + "settings_error_unknown": "Impossibile connettersi al controller Tor perché le tue impostazioni non hanno senso.", + "settings_error_automatic": "Impossibile connettersi al controller Tor. Tor Browser (disponibile da torproject.org) è in esecuzione in background?", + "settings_error_socket_port": "Impossibile connettersi al controller Tor in {}: {}.", + "settings_error_socket_file": "Impossibile connettersi al controller Tor utilizzando il file socket {}.", + "settings_error_auth": "Connesso a {}: {}, ma non può autenticarsi. Forse questo non è un controller Tor?", + "settings_error_missing_password": "Collegato al controller Tor, ma richiede una password per l'autenticazione.", + "settings_error_unreadable_cookie_file": "Collegato al controller Tor, ma la password potrebbe essere errata o l'utente non è autorizzato a leggere il file cookie.", + "settings_error_bundled_tor_not_supported": "L'uso della versione Tor fornita con OnionShare non funziona in modalità sviluppatore su Windows o macOS.", + "settings_error_bundled_tor_timeout": "Prendendo troppo tempo per connettersi a Tor. Forse non sei connesso a Internet o hai un orologio di sistema impreciso?", + "settings_error_bundled_tor_broken": "OnionShare non è riuscito a connettersi a Tor in background:\n{}", + "settings_test_success": "Collegato al controller Tor.\n\nVersione Tor: {}\nSupporta servizi onion effimeri: {}.\nSupporta l'autenticazione del client: {}.\nSupporta indirizzi .onion next-gen: {}.", + "error_tor_protocol_error": "Si è verificato un errore con Tor: {}", + "error_tor_protocol_error_unknown": "Si è verificato un errore sconosciuto con Tor", + "error_invalid_private_key": "Questo tipo di chiave privata non è supportato", + "connecting_to_tor": "In connessione alla rete Tor", + "update_available": "E' disponibile una nuova versione di OnionShare. Clicca qui per scaricarla.

    Stai usando {} e l'ultima versione è {}.", + "update_error_check_error": "Non è possibile verificare per nuove versioni: il sito OnionShare dice che l'ultima versione non è riconoscibile '{}'…", + "update_error_invalid_latest_version": "Non è possibile controllare per una nuova versione: Magari non sei connesso a Tor, o il sito OnionShare non funziona?", + "update_not_available": "Stai usando la ultima versione di OnionShare.", + "gui_tor_connection_ask": "Apri le impostazione per trovare la connessione a Tor?", + "gui_tor_connection_ask_open_settings": "Sì", + "gui_tor_connection_ask_quit": "Esci", + "gui_tor_connection_error_settings": "Prova a modificare le impostazioni di come OnionShare si connette alla rete Tor.", + "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nVerifica la connessione a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.", + "gui_tor_connection_lost": "Disconnesso da Tor.", + "gui_server_started_after_timeout": "Il timer auto-stop si è esaurito prima dell'avvio del server.\nSi prega di fare una nuova condivisione.", + "gui_server_timeout_expired": "Il timer auto-stop ha già finito.\nPer favore aggiornalo per iniziare la condivisione.", + "share_via_onionshare": "Usa OnionShare", + "gui_connect_to_tor_for_onion_settings": "Connetti a Tor per vedere le impostazioni del servizio onion", + "gui_use_legacy_v2_onions_checkbox": "Usa gli indirizzi legacy", + "gui_save_private_key_checkbox": "Usa un indirizzo persistente", + "gui_share_url_description": "1 Tutti2 con questo l'indirizzo di OnionShare possono 3 scaricare4 i tuoi file usando 5 il Browser Tor6: 7", + "gui_receive_url_description": "1 Tutti2 con questo indirizzo OnionShare possono 3 caricare4 file nel tuo computer usando 5 Tor Browser6: 7", + "gui_url_label_persistent": "Questa condivisione non si arresterà automaticamente.

    Ogni successiva condivisione riutilizza l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)", + "gui_url_label_stay_open": "Questa condivisione non si arresterà automaticamente.", + "gui_url_label_onetime": "Questa condivisione verrà interrotta dopo il primo completamento.", + "gui_url_label_onetime_and_persistent": "Questa condivisione non si arresterà automaticamente.

    Ogni condivisione successiva riutilizzerà l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)", + "gui_status_indicator_share_stopped": "Pronto per condividere", + "gui_status_indicator_share_working": "Iniziando…", + "gui_status_indicator_share_started": "Condividendo", + "gui_status_indicator_receive_stopped": "Pronto per ricevere", + "gui_status_indicator_receive_working": "Iniziando…", + "gui_status_indicator_receive_started": "Ricevendo", + "gui_file_info": "{} file, {}", + "gui_file_info_single": "{} file, {}", + "history_in_progress_tooltip": "{} in avanzamento", + "history_completed_tooltip": "{} completato", + "info_in_progress_uploads_tooltip": "{} upload(s) in avanzamento", + "info_completed_uploads_tooltip": "{} upload(s) completati", + "error_cannot_create_downloads_dir": "Non è stato possibile creare la cartella in modalità ricezione: {}", + "receive_mode_downloads_dir": "I file a te mandati appariranno in questa cartella: {}", + "receive_mode_warning": "Attenzione: La modalità ricezione permette alla gente di fare l'upload di file nel tuo computer. Alcuni file possono potenzialmente prendere il controllo del tuo computer se aperti. Apri solamente file inviati da persone di cui ti fidi, o se sai quello che stai facendo.", + "gui_receive_mode_warning": "La modalità ricezione permette alle persone di fare l'upload di file nel tuo computer.

    Alcuni file possono potenzialmente prendere il controllo del tuo computer se li apri. Apri solamente file di persone di cui ti fidi, o se sai quello che stai facendo.", + "receive_mode_upload_starting": "Upload di dimensione totale {} sta partendo", + "receive_mode_received_file": "Ricevuto: {}", + "gui_mode_share_button": "Condividi File", + "gui_mode_receive_button": "Ricevi File", + "gui_settings_receiving_label": "Impostazioni di Ricezione", + "gui_settings_downloads_label": "Salva i file in", + "gui_settings_downloads_button": "Navigare", + "gui_settings_public_mode_checkbox": "Modalità pubblica", + "systray_close_server_title": "Il server OnionShare è inattivo", + "systray_close_server_message": "Un utente ha disattivato il Server", + "systray_page_loaded_title": "La pagina di OnionShare è stata caricata", + "systray_download_page_loaded_message": "Un utente ha caricato la pagina di Download", + "systray_upload_page_loaded_message": "Un utente ha caricato la pagina di Upload", + "gui_uploads": "Storia degli Upload", + "gui_no_uploads": "Nessun Upload ancora", + "gui_clear_history": "Pulisci tutto", + "gui_upload_in_progress": "Upload iniziato {}", + "gui_upload_finished_range": "Upload eseguito {} a {}", + "gui_upload_finished": "Caricato {}", + "gui_download_in_progress": "Download iniziato {}", + "gui_open_folder_error_nautilus": "Impossibile aprire la cartella perché Nautilus non è disponibile. Il file è qui: {}" } -- cgit v1.2.3-54-g00ecf From 8ca7d38c0a2b6c7559eb068c442a4fc8b4f96c6c Mon Sep 17 00:00:00 2001 From: leonardo Date: Sun, 30 Dec 2018 23:33:33 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index ba8b280a..36044d0b 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,6 +1,6 @@ { "preparing_files": "Compressione dei file in corso.", - "give_this_url": "Dai questa URL alla persona al destinatario:", + "give_this_url": "Dai questa URL al destinatario:", "ctrlc_to_stop": "Premi Ctrl+C per fermare il server", "not_a_file": "{0:s} non è un file valido.", "other_page_loaded": "URL caricato", @@ -23,7 +23,7 @@ "gui_please_wait": "Avviato... Cliccare per interrompere.", "zip_progress_bar_format": "Compressione in corso: %p%", "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.", - "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al destinatario:", + "give_this_url_stealth": "Dai questa URL e la linea HidServAuth al destinatario:", "give_this_url_receive": "Dai questo indirizzo al mittente:", "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth con il mittente:", "not_a_readable_file": "{0:s} non è un file leggibile.", @@ -35,7 +35,7 @@ "systray_download_started_message": "Un utente ha iniziato il download dei tuoi file", "systray_download_completed_title": "Download completato", "systray_download_completed_message": "L'utente ha terminato il download dei tuoi file", - "systray_download_canceled_title": "Download cancellato", + "systray_download_canceled_title": "OnionShare Download cancellato", "systray_download_canceled_message": "l'utente ha interrotto il download", "systray_upload_started_title": "Upload con OnionShare avviato", "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer", -- cgit v1.2.3-54-g00ecf From 639d2059944293fd7850c2ee5ae684f515766220 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Sat, 29 Dec 2018 13:13:52 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/share/locale/no.json b/share/locale/no.json index 381b4e84..073461bb 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,6 +1,6 @@ { "give_this_url": "Gi denne adressen til mottakeren:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe tjeneren", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", @@ -186,5 +186,8 @@ "gui_download_in_progress": "Nedlasting startet {}", "gui_settings_language_label": "Foretrukket språk", "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg.", - "timeout_upload_still_running": "Venter på at opplastingen fullføres" + "timeout_upload_still_running": "Venter på at opplastingen fullføres", + "gui_add_files": "Legg til filer", + "gui_add_folder": "Legg til mappe", + "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger" } -- cgit v1.2.3-54-g00ecf From 04fb734944a8ce1581e1dbad6243c37f1daba21b Mon Sep 17 00:00:00 2001 From: la corneja Date: Thu, 3 Jan 2019 12:04:21 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index 3aae1951..f6f90129 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -96,13 +96,13 @@ "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", - "gui_settings_stealth_option": "Nutze Klientauthorisierung (Alt)", + "gui_settings_stealth_option": "Nutze Klientauthorisierung", "gui_settings_autoupdate_label": "Suche nach einer neueren Version", "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", "gui_settings_general_label": "Allgemeine Einstellungen", "gui_settings_sharing_label": "Freigabe-Einstellungen", - "gui_settings_connection_type_automatic_option": "Versuche auto-konfiguartion mittels des Tor Browsers", + "gui_settings_connection_type_automatic_option": "Versuche Autokonfiguration mittels Tor Browser", "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", "gui_settings_tor_bridges": "Unterstützung für Tor Bridges", @@ -112,11 +112,11 @@ "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer darf die Cookiedatei nicht lesen.", - "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird nicht nutzen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird, nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", - "settings_error_bundled_tor_broken": "OnionShare konnte in Hintergrund nicht mit Tor verbinden:\n{}", + "settings_error_bundled_tor_broken": "OnionShare konnte im Hintergrund nicht mit Tor verbinden:\n{}", "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Klient-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", - "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", + "error_tor_protocol_error": "Es gab einen Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", @@ -129,7 +129,7 @@ "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", "share_via_onionshare": "Teile es per OnionShare", "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", - "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse (alt)", + "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse", "gui_share_url_description": "Jeder mit dieser OnionShareAdresse kann deine Dateien mit dem Tor Browser herunterladen: ", "gui_receive_url_description": "Jeder mit dieser OnionShareAdresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.

    Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)", @@ -148,7 +148,7 @@ "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", "receive_mode_downloads_dir": "Dateien, die dir geschickt werden, findest du in diesem Ordner: {}", "receive_mode_warning": "Achtung: Im Empfängermodus können Leute Dateien auf deinen Computer laden. Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", - "gui_receive_mode_warning": "Im Empfängermodus können Leute Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "gui_receive_mode_warning": "Im Empfängermodus können Personen Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", "receive_mode_received_file": "Empfangen: {}", "gui_mode_share_button": "Versende Dateien", "gui_mode_receive_button": "Empfange Dateien", @@ -170,16 +170,20 @@ "gui_settings_language_label": "Bevorzugte Sprache", "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)", - "timeout_upload_still_running": "Warte bis der Upload abgeschlossen wurde", + "timeout_upload_still_running": "Warte bis Upload vollständig ist", "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.", - "gui_settings_connection_type_bundled_option": "Benutzt die in OnionShare eingebaute Tor-Version", + "gui_settings_connection_type_bundled_option": "Die integrierte Tor version von OnionShare nutzen", "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", - "gui_server_started_after_timeout": "Der Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", - "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das teilen zu starten.", + "gui_server_started_after_timeout": "Die Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", + "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das Teilen zu starten.", "gui_status_indicator_share_stopped": "Bereit zum teilen", "history_in_progress_tooltip": "{} läuft", "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", "systray_page_loaded_title": "OnionShare Seite geladen", "gui_upload_finished_range": "{} hochgeladen zu {}", - "gui_upload_finished": "{} hochgeladen" + "gui_upload_finished": "{} hochgeladen", + "gui_add_files": "Dateien hinzufügen", + "gui_add_folder": "Ordner hinzufügen", + "gui_connect_to_tor_for_onion_settings": "Verbinde dich mit Tor, um die Einstellungen für onion services zu sehen", + "gui_url_label_onetime_and_persistent": "Dieser Server wird nicht automatisch stoppen. >br>
    Jeder nachfolgende Server wird die gleiche Adresse nutzen. (Um jedes mal eine andere Adresse zu nutzen, schalte \"Nutze eine gleichbleibende Adresse\" in den Einstellungen aus.)" } -- cgit v1.2.3-54-g00ecf From 582bc912a1fce114a115e644e5453d9f6c491201 Mon Sep 17 00:00:00 2001 From: Ahmed Essam Date: Sun, 30 Dec 2018 14:09:45 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 92987bd7..f89ceb10 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -2,13 +2,13 @@ "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", "preparing_files": "ضغط الملفات.", "give_this_url": "أعط هذا العنوان للمتلقي:", - "give_this_url_stealth": "", + "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", "give_this_url_receive": "إعط هذا العنوان للمُرسِل:", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", + "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", + "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف السيرفر", + "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", + "not_a_readable_file": "{0:s} ملف لا يمكن قراءته.", + "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", "other_page_loaded": "", "close_on_timeout": "", "closing_automatically": "", -- cgit v1.2.3-54-g00ecf From 8f681a5e6d006dda8c8b8c0095af7b17155bef9e Mon Sep 17 00:00:00 2001 From: leonardo Date: Sun, 30 Dec 2018 23:36:20 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/it.json b/share/locale/it.json index 36044d0b..22934e94 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -6,7 +6,7 @@ "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica, download completato", "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare Tor: versione in fase di sviluppo", + "help_local_only": "Non usare Tor (versione in fase di sviluppo)", "help_stay_open": "Mantieni il servizio avviato anche dopo aver completato il primo download", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", @@ -36,7 +36,7 @@ "systray_download_completed_title": "Download completato", "systray_download_completed_message": "L'utente ha terminato il download dei tuoi file", "systray_download_canceled_title": "OnionShare Download cancellato", - "systray_download_canceled_message": "l'utente ha interrotto il download", + "systray_download_canceled_message": "L'utente ha interrotto il download", "systray_upload_started_title": "Upload con OnionShare avviato", "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer", "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", -- cgit v1.2.3-54-g00ecf From 6cb76700379ce1b78f94967c514fc42c72044ba0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 Jan 2019 15:17:34 -0800 Subject: Add python3 to Build-Depends --- stdeb.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdeb.cfg b/stdeb.cfg index f32730fa..de6c52c2 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [DEFAULT] Package3: onionshare -Depends3: python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy -Build-Depends: python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy +Depends3: python3, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy +Build-Depends: python3, python3-pytest, python3-flask, python3-stem, python3-pyqt5, python3-crypto, python3-socks, python3-distutils, python-nautilus, tor, obfs4proxy Suite: bionic X-Python3-Version: >= 3.5.3 -- cgit v1.2.3-54-g00ecf From 2b69a40a3a8624ba2dbbbe86af0163c31ceb4a81 Mon Sep 17 00:00:00 2001 From: Ahmed Essam Date: Thu, 3 Jan 2019 15:39:01 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index f89ceb10..14a20842 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,9 +1,9 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "ضغط الملفات.", + "preparing_files": "يتم ضغط الملفات.", "give_this_url": "أعط هذا العنوان للمتلقي:", "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", - "give_this_url_receive": "إعط هذا العنوان للمُرسِل:", + "give_this_url_receive": "إعط هذا العنوان للراسل:", "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف السيرفر", "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", -- cgit v1.2.3-54-g00ecf From 16338b8d0ef4c77e60c18432a10972221f3c3db8 Mon Sep 17 00:00:00 2001 From: emma peel Date: Thu, 3 Jan 2019 15:35:11 +0000 Subject: Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ --- share/locale/cs.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 74eb8ea3..af8c6fff 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -1,9 +1,9 @@ { - "config_onion_service": "Nastavuji onion service na portu {0:d}.", - "preparing_files": "Připravuji soubory ke sdílení.", + "config_onion_service": "Nastavuji onion požadavky na portu {0:d}.", + "preparing_files": "Probíhá komprese souborů.", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", - "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", + "give_this_url_stealth": "Sděl příjemci tuto URL a HidServAuth:", + "ctrlc_to_stop": "Stiskněte Ctrl+C pro zastavení serveru", "not_a_file": "{0:s} není soubor.", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", @@ -59,5 +59,8 @@ "settings_error_missing_password": "Připojen k ovladači Toru, ale vyžaduje heslo pro autentizaci.", "settings_error_unreadable_cookie_file": "Připojen k ovladači Toru, ale nejde se autentizovat, protože heslo je možná špatné a váš uživatel nemá povolení číst soubor cookie.", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", - "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work." + "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "give_this_url_receive": "Sděl tuto adresu odesilateli:", + "give_this_url_receive_stealth": "Sdělte tuto adresu a HidServAuth odesilateli:", + "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt" } -- cgit v1.2.3-54-g00ecf From a9dba2216f4bbb80fd92440d305a9f9e5cb4ba56 Mon Sep 17 00:00:00 2001 From: Filip Hron Date: Thu, 3 Jan 2019 15:37:31 +0000 Subject: Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ --- share/locale/cs.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index af8c6fff..84593750 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -4,7 +4,7 @@ "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", "give_this_url_stealth": "Sděl příjemci tuto URL a HidServAuth:", "ctrlc_to_stop": "Stiskněte Ctrl+C pro zastavení serveru", - "not_a_file": "{0:s} není soubor.", + "not_a_file": "{0:s} není platný soubor.", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", "large_filesize": "Varování: Posílání velkých souborů může trvat hodiny", @@ -62,5 +62,7 @@ "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", "give_this_url_receive": "Sděl tuto adresu odesilateli:", "give_this_url_receive_stealth": "Sdělte tuto adresu a HidServAuth odesilateli:", - "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt" + "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt", + "not_a_readable_file": "Soubor {0:s} není čitelný.", + "timeout_download_still_running": "Čeká se na konec stahování" } -- cgit v1.2.3-54-g00ecf From 383f9170fcffb70b90358d8c4be224ec5a09465f Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Sun, 30 Dec 2018 13:37:19 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index b759cdc4..fdb456e0 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -51,7 +51,7 @@ "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_option": "Brug klientautentifikation (forældet)", + "gui_settings_stealth_option": "Brug klientautentifikation", "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu\nkan klikke for at kopiere din HidServAuth.", "gui_settings_autoupdate_label": "Søg efter ny version", "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", @@ -111,7 +111,7 @@ "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende adresse (udgået)", + "gui_save_private_key_checkbox": "Brug en vedvarende adresse", "gui_copied_url_title": "Kopierede OnionShare-adresse", "gui_copied_hidservauth_title": "Kopierede HidServAuth", "gui_quit_title": "Klap lige hesten", @@ -185,5 +185,8 @@ "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}", - "timeout_upload_still_running": "Venter på at upload skal blive færdig" + "timeout_upload_still_running": "Venter på at upload skal blive færdig", + "gui_add_files": "Tilføj filer", + "gui_add_folder": "Tilføj mappe", + "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste" } -- cgit v1.2.3-54-g00ecf From ef3b1885c6cd2c2da3b95edb84e28646eb9eff9f Mon Sep 17 00:00:00 2001 From: xin Date: Tue, 1 Jan 2019 21:57:15 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 38580eb1..72ed6b5a 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -106,7 +106,7 @@ "gui_tor_connection_ask_quit": "Quitter", "gui_tor_connection_lost": "Déconnecté de Tor.", "share_via_onionshare": "Partager via OnionShare", - "gui_save_private_key_checkbox": "Utiliser une adresse persistante (legacy)", + "gui_save_private_key_checkbox": "Utiliser une adresse persistante", "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", @@ -154,7 +154,7 @@ "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", - "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)", + "gui_settings_stealth_option": "Utiliser l'autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée, vous pouvez maintenant\ncliquer pour copier votre HidServAuth.", "gui_settings_autoupdate_check_button": "Vérifier les mises à jour", @@ -182,5 +182,7 @@ "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré" + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré", + "gui_add_files": "Ajouter fichiers", + "gui_add_folder": "Ajouter dossier" } -- cgit v1.2.3-54-g00ecf From f33e1d087cb0fdbca87194f749df0fb837761d8f Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 4 Jan 2019 07:57:13 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index 22934e94..17d4e770 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -6,7 +6,7 @@ "other_page_loaded": "URL caricato", "closing_automatically": "Chiusura automatica, download completato", "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare Tor (versione in fase di sviluppo)", + "help_local_only": "Non usare Tor (solo per lo sviluppo)", "help_stay_open": "Mantieni il servizio avviato anche dopo aver completato il primo download", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", -- cgit v1.2.3-54-g00ecf From ef85640645b279279d80a4c606f44eea2ce426c2 Mon Sep 17 00:00:00 2001 From: Zuhualime Akoochimoya Date: Sat, 29 Dec 2018 19:05:50 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index e18c54a0..0e2c3135 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -36,7 +36,7 @@ "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", - "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", + "gui_settings_stealth_option": "Utilizar autorización de cliente", "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", @@ -99,7 +99,7 @@ "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", - "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", + "gui_save_private_key_checkbox": "Usar una dirección persistente", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Navegador Tor: ", "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", @@ -186,5 +186,8 @@ "gui_settings_language_label": "Idioma preferido", "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", - "timeout_upload_still_running": "Esperando a que se complete la subida" + "timeout_upload_still_running": "Esperando a que se complete la subida", + "gui_add_files": "Añadir Archivos", + "gui_add_folder": "Añadir Carpeta", + "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla" } -- cgit v1.2.3-54-g00ecf From d72fe680906996903206c93e3c4f6e8e7331b40b Mon Sep 17 00:00:00 2001 From: xin Date: Sun, 6 Jan 2019 00:03:35 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 72ed6b5a..275fd80a 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -18,7 +18,7 @@ "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", "gui_add": "Ajouter", "gui_delete": "Supprimer", - "gui_choose_items": "Sélectionnez", + "gui_choose_items": "Sélectionner", "gui_share_start_server": "Commencer à partager", "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier l'adresse", @@ -124,8 +124,8 @@ "history_in_progress_tooltip": "{} en cours", "history_completed_tooltip": "{} terminé", "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", - "receive_mode_warning": "Avertissement : le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", - "gui_receive_mode_warning": "Le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_warning": "Avertissement : le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", "receive_mode_received_file": "Reçu : {}", "gui_mode_share_button": "Fichiers partagés", "gui_mode_receive_button": "Fichiers reçus", -- cgit v1.2.3-54-g00ecf From 0e68ad46c71601a1e610941ec2039f6b766779db Mon Sep 17 00:00:00 2001 From: sajolida Date: Wed, 9 Jan 2019 17:06:16 +0000 Subject: Give visual feedback while starting See https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html --- install/onionshare.desktop | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install/onionshare.desktop b/install/onionshare.desktop index 3a55e498..a0861cf8 100644 --- a/install/onionshare.desktop +++ b/install/onionshare.desktop @@ -12,3 +12,5 @@ Categories=Network;FileTransfer; Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting; Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting; Keywords[de]=tor;Anonymität;Privatsphäre;Onion-Service;File-Sharing;File-Hosting; +StartupNotify=true +StartupWMClass=onionshare -- cgit v1.2.3-54-g00ecf From 72e0056cbdda1b6de3f60da554f13f687aaeea57 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 11 Jan 2019 09:05:27 +0000 Subject: Added translation using Weblate (Hebrew) --- share/locale/he.json | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 share/locale/he.json diff --git a/share/locale/he.json b/share/locale/he.json new file mode 100644 index 00000000..11c255f6 --- /dev/null +++ b/share/locale/he.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From a6de627fc86a391e3cb58f6dfe3a8edfca7266e8 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 11 Jan 2019 09:05:30 +0000 Subject: Added translation using Weblate (Japanese) --- share/locale/ja.json | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 share/locale/ja.json diff --git a/share/locale/ja.json b/share/locale/ja.json new file mode 100644 index 00000000..11c255f6 --- /dev/null +++ b/share/locale/ja.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From 8760989a7e9c438ec65969d2886ac2dd908cfd83 Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 11 Jan 2019 09:05:39 +0000 Subject: Added translation using Weblate (Shona) --- share/locale/sn.json | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 share/locale/sn.json diff --git a/share/locale/sn.json b/share/locale/sn.json new file mode 100644 index 00000000..11c255f6 --- /dev/null +++ b/share/locale/sn.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} -- cgit v1.2.3-54-g00ecf From dc45ba2b0b96610c96b2104cae96197dc81addfd Mon Sep 17 00:00:00 2001 From: PrivateLabs Date: Sat, 12 Jan 2019 02:25:12 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 14a20842..3e402ac1 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -9,23 +9,23 @@ "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", "not_a_readable_file": "{0:s} ملف لا يمكن قراءته.", "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", - "other_page_loaded": "", + "other_page_loaded": "تم تحميل العنوان", "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "closing_automatically": "توقف بسبب انتهاء التحميل", + "timeout_download_still_running": "انتظار اكتمال التحميل", + "large_filesize": "تحذير: ارسال مشاركة كبيرة قد يستغرق ساعات", "systray_menu_exit": "خروج", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "المستخدم الغاء التحميل", - "systray_upload_started_title": "onionshare تحميل بدأت", - "systray_upload_started_message": "بدأ المستخدم تحميل الملفات إلى جهاز الكمبيوتر الخاص بك", + "systray_download_completed_message": "اكمل المستخدم تحميل الملفات", + "systray_download_canceled_title": "تم الغاء التحميل", + "systray_download_canceled_message": "الغى المستخدم التحميل", + "systray_upload_started_title": "بدأ الرفع", + "systray_upload_started_message": "بدأ مستخدم رفع ملفات الى حاسوبك", "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", + "help_stay_open": "استمر في المشاركة بعد اول تحميل", + "help_shutdown_timeout": "أوقف المشاركة بعد ثواني محددة", "help_stealth": "", "help_receive": "", "help_debug": "", @@ -181,5 +181,6 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "انتظار اكتمال الرفع" } -- cgit v1.2.3-54-g00ecf From a632fd10b9c9460d57af372684f2a7bcfd441903 Mon Sep 17 00:00:00 2001 From: Zuhualime Akoochimoya Date: Sat, 12 Jan 2019 00:09:42 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index 0e2c3135..ce6c0fd2 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -80,7 +80,7 @@ "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", + "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}.\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", "error_invalid_private_key": "Este tipo de clave privada no está soportado", @@ -95,7 +95,7 @@ "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea nueva conexión compartida.", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea una nueva conexión compartida.", "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", -- cgit v1.2.3-54-g00ecf From 9a51f25bcae718b87777410f8a1b912de1e8d556 Mon Sep 17 00:00:00 2001 From: Robert Macabre Blimey Date: Sun, 13 Jan 2019 21:34:23 +0000 Subject: Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ --- share/locale/ko.json | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/share/locale/ko.json b/share/locale/ko.json index c152c33a..d4ce9d38 100644 --- a/share/locale/ko.json +++ b/share/locale/ko.json @@ -1,34 +1,34 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", + "config_onion_service": "어니언 서비스를 포트{0:d} 에서 설정하기.", + "preparing_files": "파일들을 압축하기.", + "give_this_url": "이 주소를 수신자에게 보내시오:", + "give_this_url_stealth": "이 주소와 그리고 HidServAuth 선을 수신자에게 보내시오:", + "give_this_url_receive": "이 주소를 발신자에게 보내시오:", + "give_this_url_receive_stealth": "이 주소와 그리고 HidServAuth를 발신자에 보내시오:", + "ctrlc_to_stop": "서버를 멈추기 위해 Ctrl+C 키를 누르시오", + "not_a_file": "{0:s} 는 유효하지 않은 파일입니다.", + "not_a_readable_file": "{0:s} 는 읽을수 없는 파일입니다.", + "no_available_port": "어니언 서비스를 시작하기 위한 사용 가능한 포트를 찾을수 없었습니다", + "other_page_loaded": "주소가 로드되다", + "close_on_timeout": "자동멈춤 타이머가 끝났기 때문에 정지되다", + "closing_automatically": "다운로드가 완료되었기 때문에 정지되다", + "timeout_download_still_running": "다운로드가 완료되기를 기다리는 중입니다", + "timeout_upload_still_running": "업로드가 완료되기를 기다리는 중입니다", + "large_filesize": "경고: 대용량의 자료를 보내는것은 오래 걸릴수 있습니다", "systray_menu_exit": "종료", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", + "systray_download_started_title": "어니언쉐어 다운로드가 시작됨", + "systray_download_started_message": "사용자가 당신의 파일들을 다운로딩 하기 시작했습니다", + "systray_download_completed_title": "어니언쉐어 다운로드가 완료됨", + "systray_download_completed_message": "사용자가 당신의 파일들을 다운로딩 하는것을 완료했습니다", + "systray_download_canceled_title": "어니언쉐어 다운로드가 취소됨", + "systray_download_canceled_message": "사용자가 다운로드를 취소했습니다", + "systray_upload_started_title": "어니언쉐어 업로드가 시작됨", + "systray_upload_started_message": "사용자가 파일들을 당신의 컴퓨터로 업로딩 하는것을 시작했습니다", + "help_local_only": "Tor를 사용하지 마시오 (오직 개발자용)", + "help_stay_open": "첫 다운로드 후 계속 공유하시오", + "help_shutdown_timeout": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오", + "help_stealth": "고객 허가를 사용 (고급 수준의)", + "help_receive": "그것들을 보내는것 대신 공유를 받으시오", "help_debug": "", "help_filename": "", "help_config": "", -- cgit v1.2.3-54-g00ecf From 952e036d1284403b718534ec45a1444873dbbb9d Mon Sep 17 00:00:00 2001 From: emma peel Date: Fri, 11 Jan 2019 09:14:00 +0000 Subject: Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ --- share/locale/bg.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/bg.json b/share/locale/bg.json index e8a8f3e5..256f27e0 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -8,7 +8,7 @@ "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", "not_a_file": "{0: s) не е валиден документ.", "not_a_readable_file": "{0:s) не е четаем файл.", - "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана.", + "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана", "other_page_loaded": "Адресът е зареден", "close_on_timeout": "Спряно, защото автоматично спиращият таймер приключи", "closing_automatically": "Спряно, защото свалянето приключи", -- cgit v1.2.3-54-g00ecf From 57858b85baa58515b79b787fd95574d6195d91e2 Mon Sep 17 00:00:00 2001 From: Robert Macabre Blimey Date: Sun, 13 Jan 2019 13:31:51 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index f6f90129..d8df06a9 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,15 +1,15 @@ { - "preparing_files": "Dateien werden vorbereitet.", + "preparing_files": "Dateien werden komprimiert.", "give_this_url": "Gib diese URL an den Empfänger:", "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", - "not_a_file": "{0:s} ist keine Datei.", + "not_a_file": "{0:s} ist keine gültige Datei.", "other_page_loaded": "URL geladen", "closing_automatically": "Gestoppt, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", - "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler aus die Festplatte", - "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", + "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler auf die Festplatte", + "help_filename": "Liste der zu teilenden Dateien oder Ordner", "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", "gui_add": "Hinzufügen", "gui_delete": "Löschen", @@ -28,11 +28,11 @@ "gui_settings_button_cancel": "Abbrechen", "gui_settings_button_help": "Hilfe", "gui_settings_shutdown_timeout": "Stoppe den Server bei:", - "systray_download_started_title": "OnionShareDownload begonnen", + "systray_download_started_title": "OnionShare Download begonnen", "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", "systray_download_completed_title": "OnionShare Download beendet", "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", - "systray_download_canceled_title": "OnionShareDownload abgebrochen", + "systray_download_canceled_title": "OnionShare Download abgebrochen", "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen", "gui_copy_hidservauth": "HidServAuth kopieren", "gui_canceled": "Abgebrochen", @@ -49,10 +49,10 @@ "give_this_url_receive": "Gib diese URL dem Sender:", "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", "not_a_readable_file": "{0:s} kann nicht gelesen werden.", - "no_available_port": "Konnte keinen freien Port finden, um den Onionservice zu starten", + "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onionservice zu starten", "close_on_timeout": "Wegen Zeitablaufs gestoppt", "systray_upload_started_title": "OnionShare Upload gestartet", - "systray_upload_started_message": "Ein Nutzer hat begonnen Dateien auf deinen Computer zu laden", + "systray_upload_started_message": "Ein Benutzer hat begonnen Dateien auf deinen Computer zu laden", "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", "help_receive": "Empfange Dateien anstatt sie zu senden", "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", -- cgit v1.2.3-54-g00ecf From b39eb126c06c79adc18316a9af47536565f09327 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 15 Jan 2019 21:52:42 -0800 Subject: Make it so GUI tests only run with --rungui --- tests/conftest.py | 21 +++++++++++++++------ ...ionshare_404_public_mode_skips_ratelimit_test.py | 2 ++ .../local_onionshare_404_triggers_ratelimit_test.py | 2 ++ ...re_quitting_during_share_prompts_warning_test.py | 2 ++ ...al_onionshare_receive_mode_sender_closed_test.py | 2 ++ tests/local_onionshare_receive_mode_timer_test.py | 2 ++ ...are_receive_mode_upload_non_writable_dir_test.py | 2 ++ ...mode_upload_public_mode_non_writable_dir_test.py | 2 ++ ...ionshare_receive_mode_upload_public_mode_test.py | 2 ++ tests/local_onionshare_receive_mode_upload_test.py | 2 ++ ...al_onionshare_settings_dialog_legacy_tor_test.py | 2 ++ .../local_onionshare_settings_dialog_no_tor_test.py | 2 ++ .../local_onionshare_settings_dialog_v3_tor_test.py | 2 ++ ...ionshare_share_mode_download_public_mode_test.py | 2 ++ ...onionshare_share_mode_download_stay_open_test.py | 2 ++ tests/local_onionshare_share_mode_download_test.py | 2 ++ ...cal_onionshare_share_mode_large_download_test.py | 2 ++ ...al_onionshare_share_mode_slug_persistent_test.py | 2 ++ tests/local_onionshare_share_mode_timer_test.py | 2 ++ ...al_onionshare_share_mode_timer_too_short_test.py | 2 ++ ...al_onionshare_share_mode_unreadable_file_test.py | 2 ++ tests/onionshare_790_cancel_on_second_share_test.py | 3 ++- ...ionshare_receive_mode_upload_public_mode_test.py | 1 + tests/onionshare_receive_mode_upload_test.py | 1 + tests/onionshare_share_mode_cancel_share_test.py | 1 + ...ionshare_share_mode_download_public_mode_test.py | 1 + ...onionshare_share_mode_download_stay_open_test.py | 1 + tests/onionshare_share_mode_download_test.py | 1 + tests/onionshare_share_mode_persistent_test.py | 1 + tests/onionshare_share_mode_stealth_test.py | 1 + tests/onionshare_share_mode_timer_test.py | 1 + ...onshare_share_mode_tor_connection_killed_test.py | 1 + tests/onionshare_share_mode_v2_onion_test.py | 1 + 33 files changed, 68 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 688b22d8..7aca2b2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,19 +11,28 @@ import pytest from onionshare import common, web, settings, strings def pytest_addoption(parser): + parser.addoption( + "--rungui", action="store_true", default=False, help="run GUI tests" + ) parser.addoption( "--runtor", action="store_true", default=False, help="run tor tests" ) def pytest_collection_modifyitems(config, items): - if config.getoption("--runtor"): + if not config.getoption("--runtor"): # --runtor given in cli: do not skip tor tests - return - skip_tor = pytest.mark.skip(reason="need --runtor option to run") - for item in items: - if "tor" in item.keywords: - item.add_marker(skip_tor) + skip_tor = pytest.mark.skip(reason="need --runtor option to run") + for item in items: + if "tor" in item.keywords: + item.add_marker(skip_tor) + + if not config.getoption('--rungui'): + # --rungui given in cli: do not skip GUI tests + skip_gui = pytest.mark.skip(reason="need --rungui option to run") + for item in items: + if "gui" in item.keywords: + item.add_marker(skip_gui) @pytest.fixture diff --git a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py index 11feb6f0..ebb524c2 100644 --- a/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py +++ b/tests/local_onionshare_404_public_mode_skips_ratelimit_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -16,6 +17,7 @@ class Local404PublicModeRateLimitTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, True) diff --git a/tests/local_onionshare_404_triggers_ratelimit_test.py b/tests/local_onionshare_404_triggers_ratelimit_test.py index ad49c3f8..8ed0777a 100644 --- a/tests/local_onionshare_404_triggers_ratelimit_test.py +++ b/tests/local_onionshare_404_triggers_ratelimit_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -15,6 +16,7 @@ class Local404RateLimitTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) diff --git a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py index d2fe4986..e57167c8 100644 --- a/tests/local_onionshare_quitting_during_share_prompts_warning_test.py +++ b/tests/local_onionshare_quitting_during_share_prompts_warning_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from PyQt5 import QtCore, QtTest @@ -16,6 +17,7 @@ class LocalQuittingDuringSharePromptsWarningTest(unittest.TestCase, GuiShareTest def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_sender_closed_test.py b/tests/local_onionshare_receive_mode_sender_closed_test.py index e177d2ef..bfb9499a 100644 --- a/tests/local_onionshare_receive_mode_sender_closed_test.py +++ b/tests/local_onionshare_receive_mode_sender_closed_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -15,6 +16,7 @@ class LocalReceiveModeSenderClosedTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_timer_test.py b/tests/local_onionshare_receive_mode_timer_test.py index 88002f94..0acaa4a9 100644 --- a/tests/local_onionshare_receive_mode_timer_test.py +++ b/tests/local_onionshare_receive_mode_timer_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -16,6 +17,7 @@ class LocalReceiveModeTimerTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_timer_tests(False) diff --git a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py index 7d7b2780..a1dcd679 100644 --- a/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_non_writable_dir_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -15,6 +16,7 @@ class LocalReceiveModeUnwritableTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_unwritable_dir_tests(False, True) diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py index cdc4e62a..529e0c87 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_non_writable_dir_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -16,6 +17,7 @@ class LocalReceivePublicModeUnwritableTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_unwritable_dir_tests(True, True) diff --git a/tests/local_onionshare_receive_mode_upload_public_mode_test.py b/tests/local_onionshare_receive_mode_upload_public_mode_test.py index bedb7ae2..f8bd38bd 100644 --- a/tests/local_onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/local_onionshare_receive_mode_upload_public_mode_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -16,6 +17,7 @@ class LocalReceiveModePublicModeTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(True, True) diff --git a/tests/local_onionshare_receive_mode_upload_test.py b/tests/local_onionshare_receive_mode_upload_test.py index 82baf3fd..362e3b85 100644 --- a/tests/local_onionshare_receive_mode_upload_test.py +++ b/tests/local_onionshare_receive_mode_upload_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiReceiveTest import GuiReceiveTest @@ -15,6 +16,7 @@ class LocalReceiveModeTest(unittest.TestCase, GuiReceiveTest): def tearDownClass(cls): GuiReceiveTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_receive_mode_tests(False, True) diff --git a/tests/local_onionshare_settings_dialog_legacy_tor_test.py b/tests/local_onionshare_settings_dialog_legacy_tor_test.py index ae6ce272..f32023fe 100644 --- a/tests/local_onionshare_settings_dialog_legacy_tor_test.py +++ b/tests/local_onionshare_settings_dialog_legacy_tor_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from onionshare import strings @@ -14,6 +15,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): def tearDownClass(cls): SettingsGuiBaseTest.tear_down() + @pytest.mark.gui def test_gui_legacy_tor(self): self.gui.onion = OnionStub(True, False) self.gui.reload_settings() diff --git a/tests/local_onionshare_settings_dialog_no_tor_test.py b/tests/local_onionshare_settings_dialog_no_tor_test.py index f01e049d..b34cbff3 100644 --- a/tests/local_onionshare_settings_dialog_no_tor_test.py +++ b/tests/local_onionshare_settings_dialog_no_tor_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from onionshare import strings @@ -14,6 +15,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): def tearDownClass(cls): SettingsGuiBaseTest.tear_down() + @pytest.mark.gui def test_gui_no_tor(self): self.gui.onion = OnionStub(False, False) self.gui.reload_settings() diff --git a/tests/local_onionshare_settings_dialog_v3_tor_test.py b/tests/local_onionshare_settings_dialog_v3_tor_test.py index cb10f8c9..1dd17c0f 100644 --- a/tests/local_onionshare_settings_dialog_v3_tor_test.py +++ b/tests/local_onionshare_settings_dialog_v3_tor_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from onionshare import strings @@ -14,6 +15,7 @@ class SettingsGuiTest(unittest.TestCase, SettingsGuiBaseTest): def tearDownClass(cls): SettingsGuiBaseTest.tear_down() + @pytest.mark.gui def test_gui_v3_tor(self): self.gui.onion = OnionStub(True, True) self.gui.reload_settings() diff --git a/tests/local_onionshare_share_mode_download_public_mode_test.py b/tests/local_onionshare_share_mode_download_public_mode_test.py index d6dff13a..f9a9dc4c 100644 --- a/tests/local_onionshare_share_mode_download_public_mode_test.py +++ b/tests/local_onionshare_share_mode_download_public_mode_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -15,6 +16,7 @@ class LocalShareModePublicModeTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(True, False) diff --git a/tests/local_onionshare_share_mode_download_stay_open_test.py b/tests/local_onionshare_share_mode_download_stay_open_test.py index 54d6de51..65304924 100644 --- a/tests/local_onionshare_share_mode_download_stay_open_test.py +++ b/tests/local_onionshare_share_mode_download_stay_open_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -15,6 +16,7 @@ class LocalShareModeStayOpenTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, True) diff --git a/tests/local_onionshare_share_mode_download_test.py b/tests/local_onionshare_share_mode_download_test.py index ff182740..ea16683e 100644 --- a/tests/local_onionshare_share_mode_download_test.py +++ b/tests/local_onionshare_share_mode_download_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -14,6 +15,7 @@ class LocalShareModeTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_tests(False, False) diff --git a/tests/local_onionshare_share_mode_large_download_test.py b/tests/local_onionshare_share_mode_large_download_test.py index 46e6df28..7d0595a2 100644 --- a/tests/local_onionshare_share_mode_large_download_test.py +++ b/tests/local_onionshare_share_mode_large_download_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -14,6 +15,7 @@ class LocalShareModeLargeDownloadTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_large_file_tests(False, True) diff --git a/tests/local_onionshare_share_mode_slug_persistent_test.py b/tests/local_onionshare_share_mode_slug_persistent_test.py index a1cc6972..7254f3ff 100644 --- a/tests/local_onionshare_share_mode_slug_persistent_test.py +++ b/tests/local_onionshare_share_mode_slug_persistent_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -18,6 +19,7 @@ class LocalShareModePersistentSlugTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_persistent_tests(False, True) diff --git a/tests/local_onionshare_share_mode_timer_test.py b/tests/local_onionshare_share_mode_timer_test.py index 41a6268d..e30ce4ec 100644 --- a/tests/local_onionshare_share_mode_timer_test.py +++ b/tests/local_onionshare_share_mode_timer_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -16,6 +17,7 @@ class LocalShareModeTimerTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_timer_tests(False) diff --git a/tests/local_onionshare_share_mode_timer_too_short_test.py b/tests/local_onionshare_share_mode_timer_too_short_test.py index 41c30883..8d22048d 100644 --- a/tests/local_onionshare_share_mode_timer_too_short_test.py +++ b/tests/local_onionshare_share_mode_timer_too_short_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from PyQt5 import QtCore, QtTest @@ -17,6 +18,7 @@ class LocalShareModeTimerTooShortTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_setup_tests() diff --git a/tests/local_onionshare_share_mode_unreadable_file_test.py b/tests/local_onionshare_share_mode_unreadable_file_test.py index 38a0e847..c74d7a0a 100644 --- a/tests/local_onionshare_share_mode_unreadable_file_test.py +++ b/tests/local_onionshare_share_mode_unreadable_file_test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest from .GuiShareTest import GuiShareTest @@ -14,6 +15,7 @@ class LocalShareModeUnReadableFileTest(unittest.TestCase, GuiShareTest): def tearDownClass(cls): GuiShareTest.tear_down() + @pytest.mark.gui def test_gui(self): self.run_all_common_setup_tests() self.run_all_share_mode_unreadable_file_tests() diff --git a/tests/onionshare_790_cancel_on_second_share_test.py b/tests/onionshare_790_cancel_on_second_share_test.py index b144edf3..4b7673bb 100644 --- a/tests/onionshare_790_cancel_on_second_share_test.py +++ b/tests/onionshare_790_cancel_on_second_share_test.py @@ -9,7 +9,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): @classmethod def setUpClass(cls): test_settings = { - "close_after_first_download": True + "close_after_first_download": True } cls.gui = TorGuiShareTest.set_up(test_settings) @@ -17,6 +17,7 @@ class ShareModeCancelSecondShareTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_receive_mode_upload_public_mode_test.py b/tests/onionshare_receive_mode_upload_public_mode_test.py index 275e5953..3c733f0a 100644 --- a/tests/onionshare_receive_mode_upload_public_mode_test.py +++ b/tests/onionshare_receive_mode_upload_public_mode_test.py @@ -17,6 +17,7 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): def tearDownClass(cls): TorGuiReceiveTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_receive_mode_upload_test.py b/tests/onionshare_receive_mode_upload_test.py index f9914659..493cacc7 100644 --- a/tests/onionshare_receive_mode_upload_test.py +++ b/tests/onionshare_receive_mode_upload_test.py @@ -16,6 +16,7 @@ class ReceiveModeTest(unittest.TestCase, TorGuiReceiveTest): def tearDownClass(cls): TorGuiReceiveTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_cancel_share_test.py b/tests/onionshare_share_mode_cancel_share_test.py index 5f4d6fb3..ed28ddd7 100644 --- a/tests/onionshare_share_mode_cancel_share_test.py +++ b/tests/onionshare_share_mode_cancel_share_test.py @@ -15,6 +15,7 @@ class ShareModeCancelTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_public_mode_test.py b/tests/onionshare_share_mode_download_public_mode_test.py index 672603ce..eb9adb1e 100644 --- a/tests/onionshare_share_mode_download_public_mode_test.py +++ b/tests/onionshare_share_mode_download_public_mode_test.py @@ -16,6 +16,7 @@ class ShareModePublicModeTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_stay_open_test.py b/tests/onionshare_share_mode_download_stay_open_test.py index e7e64083..93a41f1f 100644 --- a/tests/onionshare_share_mode_download_stay_open_test.py +++ b/tests/onionshare_share_mode_download_stay_open_test.py @@ -16,6 +16,7 @@ class ShareModeStayOpenTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_download_test.py b/tests/onionshare_share_mode_download_test.py index 7d414e5d..ac3dee76 100644 --- a/tests/onionshare_share_mode_download_test.py +++ b/tests/onionshare_share_mode_download_test.py @@ -15,6 +15,7 @@ class ShareModeTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_persistent_test.py b/tests/onionshare_share_mode_persistent_test.py index 86b61a81..13588a68 100644 --- a/tests/onionshare_share_mode_persistent_test.py +++ b/tests/onionshare_share_mode_persistent_test.py @@ -20,6 +20,7 @@ class ShareModePersistentSlugTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_stealth_test.py b/tests/onionshare_share_mode_stealth_test.py index b16669e6..bb9114a6 100644 --- a/tests/onionshare_share_mode_stealth_test.py +++ b/tests/onionshare_share_mode_stealth_test.py @@ -17,6 +17,7 @@ class ShareModeStealthTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_timer_test.py b/tests/onionshare_share_mode_timer_test.py index a13d2d80..7f636a71 100644 --- a/tests/onionshare_share_mode_timer_test.py +++ b/tests/onionshare_share_mode_timer_test.py @@ -17,6 +17,7 @@ class ShareModeTimerTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_tor_connection_killed_test.py b/tests/onionshare_share_mode_tor_connection_killed_test.py index 62513a12..cf48df3d 100644 --- a/tests/onionshare_share_mode_tor_connection_killed_test.py +++ b/tests/onionshare_share_mode_tor_connection_killed_test.py @@ -11,6 +11,7 @@ class ShareModeTorConnectionKilledTest(unittest.TestCase, TorGuiShareTest): } cls.gui = TorGuiShareTest.set_up(test_settings) + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() diff --git a/tests/onionshare_share_mode_v2_onion_test.py b/tests/onionshare_share_mode_v2_onion_test.py index c932abf9..18f5f058 100644 --- a/tests/onionshare_share_mode_v2_onion_test.py +++ b/tests/onionshare_share_mode_v2_onion_test.py @@ -16,6 +16,7 @@ class ShareModeV2OnionTest(unittest.TestCase, TorGuiShareTest): def tearDownClass(cls): TorGuiShareTest.tear_down() + @pytest.mark.gui @pytest.mark.tor def test_gui(self): self.run_all_common_setup_tests() -- cgit v1.2.3-54-g00ecf From 769f256b53ed42b75dde1fdda5c949a36a3c967e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 15 Jan 2019 21:54:39 -0800 Subject: Make CircleCI run GUI tests --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 70fa3b7c..22458d70 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ jobs: test-3.5: &test-template docker: - image: circleci/python:3.5.6 - + working_directory: ~/repo steps: @@ -33,7 +33,7 @@ jobs: # run tests! - run: name: run flake tests - command: | + command: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide @@ -42,7 +42,7 @@ jobs: - run: name: run tests command: | - xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ + xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ --rungui test-3.6: <<: *test-template -- cgit v1.2.3-54-g00ecf From 90afeec0a55a02375991e9ef71661addc715e867 Mon Sep 17 00:00:00 2001 From: Erwin de Keijzer Date: Wed, 16 Jan 2019 12:45:25 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 3ecc763b..37dd03c6 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -182,5 +182,7 @@ "gui_download_in_progress": "Binnenhalen gestart {}", "gui_open_folder_error_nautilus": "Kan de map niet openen omdat bestandsbeheerprogramma nautilus niet beschikbaar is. Het bestand staat hier : {}", "gui_settings_language_label": "Voorkeurstaal", - "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd." + "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.", + "gui_add_files": "Bestanden toevoegen", + "gui_add_folder": "Map toevoegen" } -- cgit v1.2.3-54-g00ecf From 8cd7dc3bedc5b1dec9f56edd476d3f400289151b Mon Sep 17 00:00:00 2001 From: CookingLeaf23 Date: Wed, 16 Jan 2019 13:53:17 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 37dd03c6..69237437 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -184,5 +184,6 @@ "gui_settings_language_label": "Voorkeurstaal", "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.", "gui_add_files": "Bestanden toevoegen", - "gui_add_folder": "Map toevoegen" + "gui_add_folder": "Map toevoegen", + "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion diensten te zien" } -- cgit v1.2.3-54-g00ecf From fd7781295cc77d6f2c1b3ba34a0298c8c6241640 Mon Sep 17 00:00:00 2001 From: Taro Tanaka Date: Wed, 16 Jan 2019 15:21:31 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 116 +++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index 11c255f6..bf6fcbb6 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -1,62 +1,62 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", - "systray_menu_exit": "", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", - "gui_add_files": "", - "gui_add_folder": "", - "gui_delete": "", - "gui_choose_items": "", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", + "config_onion_service": "{0:d}番ポートを使ってonionサービス設定中...", + "preparing_files": "ファイル圧縮中...", + "give_this_url": "このアドレスを受領者と共有して下さい。", + "give_this_url_stealth": "このアドレスとHidServAuth行を受領者と共有して下さい。", + "give_this_url_receive": "このアドレスを送信者と共有して下さい。", + "give_this_url_receive_stealth": "このアドレスとHidServAuth行を送信者と共有して下さい。", + "ctrlc_to_stop": "Ctrl+Cキーでサーバーをシャットダウンする", + "not_a_file": "{0:s}は有効なファイルではありません。", + "not_a_readable_file": "{0:s}は読めるファイルではありません。", + "no_available_port": "onionサービスを実行するための利用可能ポートを見つかりません", + "other_page_loaded": "アドレスはロードされています", + "close_on_timeout": "自動タイマーがタイムアウトしたため停止されました", + "closing_automatically": "ダウンロードが完了されたため停止されました", + "timeout_download_still_running": "ダウンロード完了待ち", + "timeout_upload_still_running": "アップロード完了待ち", + "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない", + "systray_menu_exit": "停止", + "systray_download_started_title": "OnionShareダウンロードは開始されました", + "systray_download_started_message": "ユーザーがダウンロードを開始しました", + "systray_download_completed_title": "OnionShareダウンロード完了", + "systray_download_completed_message": "ユーザーがダウンロードし終えました", + "systray_download_canceled_title": "OnionShareダウンロードは中止されました", + "systray_download_canceled_message": "ユーザーがダウンロードを中止しました", + "systray_upload_started_title": "OnionShareアップロードは開始されました", + "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました", + "help_local_only": "Torを使わない(開発利用のみ)", + "help_stay_open": "最初ダウンロード後に共有し続けます", + "help_shutdown_timeout": "数秒後に共有が停止されます", + "help_stealth": "クライアント認証を使う(上級者向け)", + "help_receive": "送信の代わりに受信を優先する", + "help_debug": "OnionShareのエラーを標準出力に、Webのエラーをディスクに記録する", + "help_filename": "共有するファイルとフォルダの一覧", + "help_config": "カスタムJSON設定ファイルの位置(任意)", + "gui_drag_and_drop": "共有を始めるにはファイルやフォルダをドラッグアンドドロップしてください", + "gui_add": "追加", + "gui_add_files": "ファイルを追加", + "gui_add_folder": "フォルダを追加", + "gui_delete": "削除", + "gui_choose_items": "選択", + "gui_share_start_server": "共有を開始する", + "gui_share_stop_server": "共有を停止する", + "gui_share_stop_server_shutdown_timeout": "共有を停止中です(残り{}秒)", + "gui_share_stop_server_shutdown_timeout_tooltip": "{}に自動停止します", + "gui_receive_start_server": "受信モードを開始", + "gui_receive_stop_server": "受信モードを停止", + "gui_receive_stop_server_shutdown_timeout": "受信モードを停止中(残り{}秒)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "{}に自動停止します", + "gui_copy_url": "アドレスをコピー", + "gui_copy_hidservauth": "HidServAuthをコピー", + "gui_downloads": "ダウンロード履歴", + "gui_no_downloads": "まだダウンロードがありません", + "gui_canceled": "キャンセルされました", + "gui_copied_url_title": "OnionShareのアドレスをコピーしました", + "gui_copied_url": "OnionShareのアドレスをクリップボードへコピーしました", + "gui_copied_hidservauth_title": "HidServAuthをコピーしました", + "gui_copied_hidservauth": "HidServAuthの行をクリップボードへコピーしました", + "gui_please_wait": "実行中… クリックでキャンセルします。", + "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", "version_string": "", -- cgit v1.2.3-54-g00ecf From 5a5eda00b1196b5f273fce9cfca17d485d4bd028 Mon Sep 17 00:00:00 2001 From: Koki Kawasaki Date: Fri, 18 Jan 2019 14:00:59 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index bf6fcbb6..10865a47 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -57,8 +57,8 @@ "gui_copied_hidservauth": "HidServAuthの行をクリップボードへコピーしました", "gui_please_wait": "実行中… クリックでキャンセルします。", "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", + "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)", + "gui_download_upload_progress_eta": "%p% {0:s} 終了予定:{1:s}", "version_string": "", "gui_quit_title": "", "gui_share_quit_warning": "", -- cgit v1.2.3-54-g00ecf From 9f3fa51fc56a381755ee22db30805b10eac68b7d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 18 Jan 2019 14:37:18 -0800 Subject: Reorder args when running tests in CircleCI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 22458d70..accbc808 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ jobs: - run: name: run tests command: | - xvfb-run pytest --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ --rungui + xvfb-run pytest --rungui --cov=onionshare --cov=onionshare_gui --cov-report=term-missing -vvv tests/ test-3.6: <<: *test-template -- cgit v1.2.3-54-g00ecf From 798f54f05aed1bf8bf3713357c3aa8b7a52b1c34 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 18 Jan 2019 14:39:25 -0800 Subject: Update tests section in BUILD.md to give instructions on using --rungui to run GUI tests --- BUILD.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 1e135251..6073d1a9 100644 --- a/BUILD.md +++ b/BUILD.md @@ -155,10 +155,16 @@ Then you can run `pytest` against the `tests/` directory. pytest tests/ ``` +You can run GUI tests like this: + +```sh +pytest --rungui tests/ +``` + If you would like to also run the GUI unit tests in 'tor' mode, start Tor Browser in the background, then run: ```sh -pytest --runtor tests/ +pytest --rungui --runtor tests/ ``` Keep in mind that the Tor tests take a lot longer to run than local mode, but they are also more comprehensive. @@ -166,5 +172,5 @@ Keep in mind that the Tor tests take a lot longer to run than local mode, but th You can also choose to wrap the tests in `xvfb-run` so that a ton of OnionShare windows don't pop up on your desktop (you may need to install the `xorg-x11-server-Xvfb` package), like this: ```sh -xvfb-run pytest tests/ +xvfb-run pytest --rungui tests/ ``` -- cgit v1.2.3-54-g00ecf From bb1577a4b2730d659efb00b0729d0d6dffe7866e Mon Sep 17 00:00:00 2001 From: Filip Hron Date: Sat, 19 Jan 2019 07:22:27 +0000 Subject: Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ --- share/locale/cs.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 84593750..25f7582c 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -21,7 +21,7 @@ "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", - "gui_downloads": "Stahování:", + "gui_downloads": "Historie stahování:", "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", @@ -63,6 +63,20 @@ "give_this_url_receive": "Sděl tuto adresu odesilateli:", "give_this_url_receive_stealth": "Sdělte tuto adresu a HidServAuth odesilateli:", "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt", - "not_a_readable_file": "Soubor {0:s} není čitelný.", - "timeout_download_still_running": "Čeká se na konec stahování" + "not_a_readable_file": "{0:s} soubor není čitelný.", + "timeout_download_still_running": "Čeká se na konec stahování", + "systray_menu_exit": "Ukončit", + "systray_download_started_title": "Stahování pomocí OnionShare začalo", + "systray_download_started_message": "Někdo stahuje vaše soubory", + "systray_download_completed_title": "Stahování pomocí OnionShare skončilo", + "systray_download_completed_message": "Uživatel dokončil stahování vašich souborů", + "systray_download_canceled_title": "Stahování pomocí OnionShare bylo zrušeno", + "systray_download_canceled_message": "Uživatel přerušil stahování souboru", + "systray_upload_started_title": "Začalo nahrávání pomocí OnionShare ", + "systray_upload_started_message": "Někdo právě začal nahrávat soubory na váš počítač", + "gui_share_stop_server_shutdown_timeout": "Zastavit sdílení ({}s zbývá)", + "gui_receive_start_server": "Spustit mód přijímání", + "gui_receive_stop_server": "Zastavit přijímání", + "gui_receive_stop_server_shutdown_timeout": "Zastavit mód přijímání ({}s zbývá)", + "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token" } -- cgit v1.2.3-54-g00ecf From 8571b5ad4068aee5a2bd111026cd20881a1fb1be Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 19 Jan 2019 07:25:11 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 3e402ac1..650be35b 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,9 +1,9 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "يتم ضغط الملفات.", + "preparing_files": "جاري ضغط الملفات.", "give_this_url": "أعط هذا العنوان للمتلقي:", "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", - "give_this_url_receive": "إعط هذا العنوان للراسل:", + "give_this_url_receive": "اعط هذا العنوان للمرسل:", "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف السيرفر", "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", -- cgit v1.2.3-54-g00ecf From 42233aa7b4a17d8ce1730e9c9e7de591027795f9 Mon Sep 17 00:00:00 2001 From: Deleted User Date: Sat, 19 Jan 2019 07:25:52 +0000 Subject: Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ --- share/locale/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 650be35b..5924eb5d 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -5,7 +5,7 @@ "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", "give_this_url_receive": "اعط هذا العنوان للمرسل:", "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", - "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف السيرفر", + "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف الخادم", "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", "not_a_readable_file": "{0:s} ملف لا يمكن قراءته.", "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", -- cgit v1.2.3-54-g00ecf From 10f89afda5ec22ea01630304b27e350d6a102aa7 Mon Sep 17 00:00:00 2001 From: emma peel Date: Sat, 19 Jan 2019 07:24:13 +0000 Subject: Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ --- share/locale/cs.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/locale/cs.json b/share/locale/cs.json index 25f7582c..3eb03198 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -21,7 +21,7 @@ "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", - "gui_downloads": "Historie stahování:", + "gui_downloads": "Historie stahování", "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", @@ -72,7 +72,7 @@ "systray_download_completed_message": "Uživatel dokončil stahování vašich souborů", "systray_download_canceled_title": "Stahování pomocí OnionShare bylo zrušeno", "systray_download_canceled_message": "Uživatel přerušil stahování souboru", - "systray_upload_started_title": "Začalo nahrávání pomocí OnionShare ", + "systray_upload_started_title": "Začalo nahrávání pomocí OnionShare", "systray_upload_started_message": "Někdo právě začal nahrávat soubory na váš počítač", "gui_share_stop_server_shutdown_timeout": "Zastavit sdílení ({}s zbývá)", "gui_receive_start_server": "Spustit mód přijímání", -- cgit v1.2.3-54-g00ecf From 2761a09fd2d3589a887e4911e9f1e62126f68633 Mon Sep 17 00:00:00 2001 From: CookingLeaf23 Date: Wed, 16 Jan 2019 13:58:08 +0000 Subject: Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ --- share/locale/nl.json | 72 ++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/share/locale/nl.json b/share/locale/nl.json index 69237437..39c5ba9f 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -23,10 +23,10 @@ "help_stay_open": "Blijven delen na afronden van eerste download", "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", - "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", - "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", + "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer om het delen te starten", "gui_add": "Toevoegen", "gui_delete": "Verwijder", "gui_choose_items": "Kies", @@ -41,22 +41,22 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (berekenen)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", + "gui_share_quit_warning": "Je bent in het proces van bestanden versturen. Weet je zeker dat je OnionShare af wilt sluiten?", "gui_quit_warning_quit": "Afsluiten", - "gui_quit_warning_dont_quit": "Niet afsluiten", - "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", + "gui_quit_warning_dont_quit": "Annuleren", + "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", "zip_progress_bar_format": "Comprimeren: %p%", "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", - "gui_settings_stealth_option": "Gebruik client authorization (achterhaald)", - "gui_settings_autoupdate_label": "Controleer op updates", + "gui_settings_stealth_option": "Gebruik cliëntautorisatie", + "gui_settings_autoupdate_label": "Controleer op nieuwe versies", "gui_settings_autoupdate_option": "Laat me weten als er een nieuwe versie beschikbaar is", "gui_settings_autoupdate_timestamp": "Laatste controle: {}", "gui_settings_autoupdate_timestamp_never": "Nooit", "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie", "gui_settings_sharing_label": "Instelling voor delen", - "gui_settings_close_after_first_download_option": "Stop delen na eerste download", + "gui_settings_close_after_first_download_option": "Stop met delen na eerste download", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare", "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser", @@ -67,13 +67,13 @@ "gui_settings_socket_file_label": "Socket bestand", "gui_settings_socks_label": "SOCKS poort", "gui_settings_authenticate_label": "Tor authenticatie instellingen", - "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", + "gui_settings_authenticate_no_auth_option": "Geen authenticatie, of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", "gui_settings_password_label": "Wachtwoord", "gui_settings_button_save": "Opslaan", "gui_settings_button_cancel": "Annuleren", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout": "Stop delen om:", + "gui_settings_shutdown_timeout": "Stop het delen om:", "settings_saved": "Instellingen opgeslagen in {}", "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat je instellingen nergens op slaan.", "settings_error_automatic": "Kon geen verbinding maken met de Tor controller. Draait Tor Browser (beschikbaar via torproject.org) in de achtergrond?", @@ -82,15 +82,15 @@ "settings_error_auth": "Verbonden met {}:{}, maar kan niet authenticeren. Misschien is het geen Tor controller?", "settings_error_missing_password": "Verbonden met Tor controller, maar het heeft een wachtwoord nodig voor authenticatie.", "settings_error_unreadable_cookie_file": "Verbonden met de Tor controller, maar het wachtwoord kan onjuist zijn, of je gebruiker heeft geen toestemming om het cookie bestand te lezen.", - "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de developer mode op Windows of macOS.", + "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de ontwikkelaarsmodus op Windows of macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", - "update_available": "Er is een nieuwe versie van OnionShare beschikbaar. Klik hier om te updaten.

    Je hebt nu {} en de laatste versie is {}.", + "update_available": "Nieuwe OnionShare is uitgekomen. Klik hier om hem te krijgen.

    Jij gebruikt {} and de laatste is {}.", "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", - "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of is de OnionShare website is niet beschikbaar?", + "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of de OnionShare website is niet beschikbaar?", "update_not_available": "Je draait de laatst beschikbare OnionShare.", "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", "gui_tor_connection_ask_open_settings": "Ja", @@ -106,21 +106,21 @@ "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer", "help_receive": "Bestanden ontvangen in plaats van ze versturen", "timeout_upload_still_running": "Wachten op voltooiing van de upload", - "gui_share_start_server": "Start delen", - "gui_share_stop_server": "Stop delen", - "gui_share_stop_server_shutdown_timeout": "Stop Delen ({}s resterend)", + "gui_share_start_server": "Start met delen", + "gui_share_stop_server": "Stop met delen", + "gui_share_stop_server_shutdown_timeout": "Stop met Delen ({}s resterend)", "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer eindigt bij {}", - "gui_receive_start_server": "Start Ontvangst Mode", - "gui_receive_stop_server": "Stop Ontvangst Mode", - "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangst Mode ({}s resterend)", + "gui_receive_start_server": "Start Ontvangstmodus", + "gui_receive_stop_server": "Stop Ontvangstmodus", + "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangstmodus ({}s resterend)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer stopt bij {}", "gui_no_downloads": "Nog Geen Downloads", - "gui_copied_url_title": "Gekopieerd OnionShare Adres", + "gui_copied_url_title": "Gekopieerd OnionShare Adres", "gui_copied_hidservauth_title": "HidServAuth gekopieerd", "gui_quit_title": "Niet zo snel", - "gui_receive_quit_warning": "Je ben bezig files te ontvangen. Weet je zeker dat je OnionShare wilt stoppen?", - "gui_settings_whats_this": "1What is dit?2", - "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je klikken om je HidServAuth te kopiëren.", + "gui_receive_quit_warning": "Je bent in het proces van bestanden ontvangen. Weet je zeker dat je OnionShare af wilt sluiten?", + "gui_settings_whats_this": "1Wat is dit?2", + "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je \nklikken om je HidServAuth te kopiëren.", "gui_settings_general_label": "Algemene instellingen", "gui_settings_tor_bridges": "Tor bridge ondersteuning", "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", @@ -131,13 +131,13 @@ "gui_settings_meek_lite_expensive_warning": "Waarschuwing: De meek_lite bridges zijn erg kostbaar voor het Tor Project om uit te voeren.

    Gebruik ze alleen als je niet direct met Tor kan verbinden, via obfs4 transports, of andere normale bridges.", "gui_settings_tor_bridges_custom_radio_option": "Gebruik custom bridges", "gui_settings_tor_bridges_custom_label": "Je kan bridges krijgen via 1https://bridges.torproject.org2", - "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. Controleer ze of voeg andere toe.", + "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. \nControleer ze of voeg andere toe.", "gui_settings_shutdown_timeout_checkbox": "Gebruik auto-stop timer", "error_tor_protocol_error_unknown": "Er was een onbekende fout met Tor", "error_invalid_private_key": "Dit type privésleutel wordt niet ondersteund", "gui_tor_connection_lost": "De verbinding met Tor is verbroken.", "gui_use_legacy_v2_onions_checkbox": "Gebruik ouderwetse adressen", - "gui_save_private_key_checkbox": "Gebruik een blijvend adres (achterhaald)", + "gui_save_private_key_checkbox": "Gebruik een blijvend adres", "gui_share_url_description": "1Iedereen2 met dit OnionShare adres kan je bestanden 3binnenhalen4 met de 5Tor Browser6: ", "gui_receive_url_description": "1Iedereen2 met dit OnionShare adres kan bestanden op je computer 3plaatsen4 met de 5Tor Browser6: 7", "gui_url_label_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", @@ -156,7 +156,7 @@ "history_completed_tooltip": "{} klaar", "info_in_progress_uploads_tooltip": "{} upload(s) zijn bezig", "info_completed_uploads_tooltip": "de {} upload(s) zijn klaar", - "error_cannot_create_downloads_dir": "Kon de ontvangst mode map niet maken: {}", + "error_cannot_create_downloads_dir": "Kon de ontvangst modus map niet aanmaken: {}", "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}", "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten.

    Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", @@ -167,23 +167,23 @@ "gui_settings_receiving_label": "Instellingen voor Ontvangen", "gui_settings_downloads_label": "Sla bestanden op naar", "gui_settings_downloads_button": "Surf", - "gui_settings_public_mode_checkbox": "Publieke mode", + "gui_settings_public_mode_checkbox": "Openbaarmodus", "systray_close_server_title": "OnionShare Server Afgesloten", "systray_close_server_message": "Een gebruiker heeft de server gestopt", "systray_page_loaded_title": "OnionShare Pagina Geladen", "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen", "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen", - "gui_uploads": "Upload geschiedenis", - "gui_no_uploads": "Er zijn nog geen uploads", - "gui_clear_history": "Wis alles", - "gui_upload_in_progress": "Upload is gestart{}", + "gui_uploads": "Upload Geschiedenis", + "gui_no_uploads": "Er Zijn Nog Geen Uploads", + "gui_clear_history": "Wis Alles", + "gui_upload_in_progress": "Upload Is Gestart{}", "gui_upload_finished_range": "{} is naar {} gestuurd", "gui_upload_finished": "Verzonden {}", - "gui_download_in_progress": "Binnenhalen gestart {}", - "gui_open_folder_error_nautilus": "Kan de map niet openen omdat bestandsbeheerprogramma nautilus niet beschikbaar is. Het bestand staat hier : {}", + "gui_download_in_progress": "Downloaden Gestart {}", + "gui_open_folder_error_nautilus": "Kan de map niet openen omdat nautilus niet beschikbaar is. Het bestand staat hier: {}", "gui_settings_language_label": "Voorkeurstaal", "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.", - "gui_add_files": "Bestanden toevoegen", - "gui_add_folder": "Map toevoegen", - "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion diensten te zien" + "gui_add_files": "Voeg bestanden toe", + "gui_add_folder": "Voeg map toe", + "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien" } -- cgit v1.2.3-54-g00ecf From 1afd32bef35b4bf017b2a38846d0d6792d6e605d Mon Sep 17 00:00:00 2001 From: unmixabledoze Date: Fri, 18 Jan 2019 21:02:14 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/locale/de.json b/share/locale/de.json index d8df06a9..8021cfed 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -29,7 +29,7 @@ "gui_settings_button_help": "Hilfe", "gui_settings_shutdown_timeout": "Stoppe den Server bei:", "systray_download_started_title": "OnionShare Download begonnen", - "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", + "systray_download_started_message": "Ein Nutzer hat begonnen, deine Dateien herunterzuladen", "systray_download_completed_title": "OnionShare Download beendet", "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", "systray_download_canceled_title": "OnionShare Download abgebrochen", @@ -51,8 +51,8 @@ "not_a_readable_file": "{0:s} kann nicht gelesen werden.", "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onionservice zu starten", "close_on_timeout": "Wegen Zeitablaufs gestoppt", - "systray_upload_started_title": "OnionShare Upload gestartet", - "systray_upload_started_message": "Ein Benutzer hat begonnen Dateien auf deinen Computer zu laden", + "systray_upload_started_title": "OnionShare Upload wurde gestartet", + "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", "help_receive": "Empfange Dateien anstatt sie zu senden", "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", @@ -82,7 +82,7 @@ "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {})", "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", "gui_no_downloads": "Bisher keine Downloads", - "gui_copied_url_title": "OnionShareadresse kopiert", + "gui_copied_url_title": "OnionShare-Adresse kopiert", "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert", "gui_download_upload_progress_complete": "%p%, {0:s} vergangen.", "gui_download_upload_progress_starting": "{0:s}, %p% (berechne)", @@ -93,7 +93,7 @@ "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", "zip_progress_bar_format": "Komprimiere: %p%", - "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", + "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", "gui_settings_stealth_option": "Nutze Klientauthorisierung", -- cgit v1.2.3-54-g00ecf From 3921aa2aa4389b41658ee18d7ac243ab99273b2f Mon Sep 17 00:00:00 2001 From: la corneja Date: Sat, 19 Jan 2019 22:41:22 +0000 Subject: Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ --- share/locale/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/de.json b/share/locale/de.json index 8021cfed..8e9bd634 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -115,7 +115,7 @@ "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird, nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", "settings_error_bundled_tor_broken": "OnionShare konnte im Hintergrund nicht mit Tor verbinden:\n{}", - "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Klient-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", "error_tor_protocol_error": "Es gab einen Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", -- cgit v1.2.3-54-g00ecf From c44d656c66f9b7fbdb7b0cfbeb0938de8bd092b4 Mon Sep 17 00:00:00 2001 From: Taro Tanaka Date: Fri, 18 Jan 2019 14:03:14 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 96 ++++++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index 10865a47..5503f429 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -15,7 +15,7 @@ "timeout_download_still_running": "ダウンロード完了待ち", "timeout_upload_still_running": "アップロード完了待ち", "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない", - "systray_menu_exit": "停止", + "systray_menu_exit": "終了", "systray_download_started_title": "OnionShareダウンロードは開始されました", "systray_download_started_message": "ユーザーがダウンロードを開始しました", "systray_download_completed_title": "OnionShareダウンロード完了", @@ -58,57 +58,57 @@ "gui_please_wait": "実行中… クリックでキャンセルします。", "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。", "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)", - "gui_download_upload_progress_eta": "%p% {0:s} 終了予定:{1:s}", + "gui_download_upload_progress_eta": "{0:s} 終了予定:{1:s}、%p%", "version_string": "", - "gui_quit_title": "", + "gui_quit_title": "ちょっと待って", "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?", "gui_quit_warning_quit": "", "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", + "error_rate_limit": "誰かが何度も間違えたアドレスをアクセスして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスに送って下さい。", + "zip_progress_bar_format": "圧縮中: %p%", + "error_stealth_not_supported": "クライアント認証を使用するのに、少なくともTor 0.2.9.1-alpha (それともTor Browser 6.5)とpython3-stem 1.5.0が必要です。", + "error_ephemeral_not_supported": "OnionShareは少なくともTor 0.2.7.1とpython3-stem 1.4.0が必要です。", + "gui_settings_window_title": "設定", + "gui_settings_whats_this": "これは何ですか?", + "gui_settings_stealth_option": "クライアント認証を使用", + "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。", + "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする", + "gui_settings_autoupdate_option": "更新通知を起動します", + "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}", + "gui_settings_autoupdate_timestamp_never": "したことがない", + "gui_settings_autoupdate_check_button": "更新をチェックする", + "gui_settings_general_label": "一般的設定", + "gui_settings_sharing_label": "共有設定", + "gui_settings_close_after_first_download_option": "最初のダウンロード後に停止する", + "gui_settings_connection_type_label": "OnionShareがどうやってTorと接続して欲しい?", + "gui_settings_connection_type_bundled_option": "OnionShareに組み込まれるTorバージョンを使用する", + "gui_settings_connection_type_automatic_option": "Torブラウザと自動設定してみる", + "gui_settings_connection_type_control_port_option": "コントロールポートを使用して接続する", + "gui_settings_connection_type_socket_file_option": "ソケットファイルを使用して接続する", + "gui_settings_connection_type_test_button": "Torへの接続をテストする", + "gui_settings_control_port_label": "コントロールポート", + "gui_settings_socket_file_label": "ソケットファイル", + "gui_settings_socks_label": "SOCKSポート", + "gui_settings_authenticate_label": "Tor認証の設定", + "gui_settings_authenticate_no_auth_option": "認証なし、それともクッキー認証", + "gui_settings_authenticate_password_option": "パスワード", + "gui_settings_password_label": "パスワード", + "gui_settings_tor_bridges": "Torブリッジサポート", + "gui_settings_tor_bridges_no_bridges_radio_option": "ブリッジを使用しない", + "gui_settings_tor_bridges_obfs4_radio_option": "組み込みのobs4 pluggable transportを使用する", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "組み込みのobs4 pluggable transportを使用する(obsf4proxy必要)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "組み込みのmeek_lite (Azure) pluggable transportを使用する", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "組み込みのmeek_lite (Azure) pluggable transportを使用する(obsf4proxy必要)", + "gui_settings_meek_lite_expensive_warning": "警告:meek_liteブリッジはTor Projectにとって維持費がかさむ

    直接にTorと接続できない場合、あるいはobsf4ブリッジや他のブリッジが使用できない場合のみに使って下さい。", + "gui_settings_tor_bridges_custom_radio_option": "カスタムブリッジを使用する", + "gui_settings_tor_bridges_custom_label": "https://bridges.torproject.orgからブリッジを入手できます", + "gui_settings_tor_bridges_invalid": "全ての追加したブリッジは機能しませんでした。\n再確認して、あるいは他のを追加して下さい。", + "gui_settings_button_save": "保存", + "gui_settings_button_cancel": "キャンセル", + "gui_settings_button_help": "ヘルプ", + "gui_settings_shutdown_timeout_checkbox": "自動停止タイマーを使用する", + "gui_settings_shutdown_timeout": "共有を停止する時間:", "settings_error_unknown": "", "settings_error_automatic": "", "settings_error_socket_port": "", -- cgit v1.2.3-54-g00ecf From 60d0ae789649aabe9d1ee256842cbe4c286f72b5 Mon Sep 17 00:00:00 2001 From: Koki Kawasaki Date: Fri, 18 Jan 2019 14:03:24 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index 5503f429..acddc8c2 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -59,12 +59,12 @@ "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。", "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)", "gui_download_upload_progress_eta": "{0:s} 終了予定:{1:s}、%p%", - "version_string": "", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "ちょっと待って", - "gui_share_quit_warning": "", + "gui_share_quit_warning": "ファイルを送信中です。本当にOnionShareを終了しますか?", "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", + "gui_quit_warning_quit": "終了", + "gui_quit_warning_dont_quit": "キャンセル", "error_rate_limit": "誰かが何度も間違えたアドレスをアクセスして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスに送って下さい。", "zip_progress_bar_format": "圧縮中: %p%", "error_stealth_not_supported": "クライアント認証を使用するのに、少なくともTor 0.2.9.1-alpha (それともTor Browser 6.5)とpython3-stem 1.5.0が必要です。", -- cgit v1.2.3-54-g00ecf From f6157356db275368d1f2ae8292778f44e4574cec Mon Sep 17 00:00:00 2001 From: R Date: Fri, 18 Jan 2019 19:43:29 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 107 ++++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index d45ea27c..21ebbec5 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -1,73 +1,73 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "config_onion_service": "آماده سازی سرویس onion روی پورت {0:d}.", + "preparing_files": "فشرده سازی فایل ها.", + "give_this_url": "دادن این آدرس به گیرنده:", + "give_this_url_stealth": "دادن این آدرس و خط HidServAuth به گیرنده:", + "give_this_url_receive": "دادن این آدرس به ارسال کننده:", + "give_this_url_receive_stealth": "دادن این آدرس و HidServAuth به ارسال کننده:", + "ctrlc_to_stop": "برای توقف سرور Ctrl+C را فشار دهید", + "not_a_file": "{0:s} یک فایل معتبر نمی باشد.", + "not_a_readable_file": "{0:s} قابل خواندن نمی باشد.", + "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد.", + "other_page_loaded": "آدرس بارگذاری شد", "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "closing_automatically": "متوقف شد چون دانلود به پایان رسید", + "timeout_download_still_running": "انتظار برای تکمیل دانلود", + "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد", "systray_menu_exit": "خروج", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", + "systray_download_started_title": "دانلود OnionShare آغاز شد", + "systray_download_started_message": "یک کاربر شروع به دانلود فایل های شما کرد", + "systray_download_completed_title": "دانلود OnionShare به پایان رسید", + "systray_download_completed_message": "دانلود فایل های شما توسط کاربر به پایان رسید", + "systray_download_canceled_title": "دانلود OnionShare لغو شد", + "systray_download_canceled_message": "کاربر دانلود را لغو کرد", + "systray_upload_started_title": "آپلود OnionShare آغاز شد", + "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است", "help_local_only": "", - "help_stay_open": "", + "help_stay_open": "ادامه اشتراک گذاری پس از اولین دانلود", "help_shutdown_timeout": "", "help_stealth": "", "help_receive": "", "help_debug": "", - "help_filename": "", + "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری", "help_config": "", "gui_drag_and_drop": "", "gui_add": "افزودن", - "gui_delete": "Delete", - "gui_choose_items": "گزینش", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", + "gui_delete": "حذف", + "gui_choose_items": "انتخاب", + "gui_share_start_server": "شروع اشتراک گذاری", + "gui_share_stop_server": "توقف اشتراک گذاری", + "gui_share_stop_server_shutdown_timeout": "توقف اشتراک گذاری ({} ثانیه باقیمانده)", "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", + "gui_receive_start_server": "شروع حالت دریافت", + "gui_receive_stop_server": "توقف حالت دریافت", "gui_receive_stop_server_shutdown_timeout": "", "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", + "gui_copy_url": "کپی آدرس", + "gui_copy_hidservauth": "کپی HidServAuth", + "gui_downloads": "دانلود تاریخچه", "gui_no_downloads": "", "gui_canceled": "لغو شده", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_copied_url_title": "آدرس OnionShare کپی شد", + "gui_copied_url": "آدرس OnionShare بر کلیپ بورد کپی شد", + "gui_copied_hidservauth_title": "HidServAuth کپی شد", + "gui_copied_hidservauth": "خط HidServAuth بر کلیپ بورد کپی شد", + "gui_please_wait": "در حال آغاز... برای لغو کلیک کنید.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_quit_title": "نه به این سرعت", + "gui_share_quit_warning": "شما در پروسه ارسال فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", + "gui_receive_quit_warning": "شما در پروسه دریافت فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_quit_warning_quit": "خروج", "gui_quit_warning_dont_quit": "لغو", "error_rate_limit": "", - "zip_progress_bar_format": "", + "zip_progress_bar_format": "فشرده سازی: %p%", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", "gui_settings_window_title": "تنظیمات", - "gui_settings_whats_this": "", + "gui_settings_whats_this": "این چیست؟", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", @@ -75,8 +75,8 @@ "gui_settings_autoupdate_timestamp": "", "gui_settings_autoupdate_timestamp_never": "هرگز", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "تنظیمات عمومی", - "gui_settings_sharing_label": "", + "gui_settings_general_label": "تنظیمات کلی", + "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", "gui_settings_connection_type_bundled_option": "", @@ -86,13 +86,13 @@ "gui_settings_connection_type_test_button": "", "gui_settings_control_port_label": "", "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", + "gui_settings_socks_label": "پورت SOCKS", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "Password", - "gui_settings_password_label": "Password", + "gui_settings_authenticate_password_option": "رمز عبور", + "gui_settings_password_label": "رمز عبور", "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج", "gui_settings_tor_bridges_obfs4_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", @@ -178,8 +178,11 @@ "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", - "gui_download_in_progress": "", + "gui_download_in_progress": "دانلود آغاز شد {}", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "زبان ترجیحی", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "انتظار برای تکمیل آپلود", + "gui_add_files": "افزودن فایل ها", + "gui_add_folder": "افزودن پوشه" } -- cgit v1.2.3-54-g00ecf From dbcdf6e3f09ef06ff0ca720c8eda311620b8a8d3 Mon Sep 17 00:00:00 2001 From: unmixabledoze Date: Fri, 18 Jan 2019 20:42:24 +0000 Subject: Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ --- share/locale/pl.json | 205 ++++++++++++++++++++++++++------------------------- 1 file changed, 104 insertions(+), 101 deletions(-) diff --git a/share/locale/pl.json b/share/locale/pl.json index d24a0818..f10a30ce 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -1,128 +1,128 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "config_onion_service": "Konfiguruję usługę onion na porcie {0:d}.", + "preparing_files": "Kompresuję pliki.", + "give_this_url": "Przekaż ten adres odbiorcy:", + "give_this_url_stealth": "Przekaż ten adres i linijkę HidServAuth odbiorcy:", + "give_this_url_receive": "Przekaż ten adres do nadawcy:", + "give_this_url_receive_stealth": "Przekaż ten adres i linijkę HidServAuth do nadawcy:", + "ctrlc_to_stop": "Przyciśnij kombinację klawiszy Ctrl i C aby zatrzymać serwer", + "not_a_file": "{0:s} nie jest prawidłowym plikiem.", + "not_a_readable_file": "{0:s} nie jest plikiem do odczytu.", + "no_available_port": "Nie można znaleźć dostępnego portu aby włączyć usługę onion", + "other_page_loaded": "Adres został wczytany", + "close_on_timeout": "Zatrzymano, gdyż upłynął czas", + "closing_automatically": "Zatrzymano, gdyż pobieranie zostało ukończone", + "timeout_download_still_running": "Czekam na ukończenie pobierania", + "large_filesize": "Uwaga: Wysyłanie dużego pliku może zająć kilka godzin", "systray_menu_exit": "Wyjście", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", + "systray_download_started_title": "Pobieranie OnionShare zostało rozpoczęte", + "systray_download_started_message": "Użytkownik rozpoczął ściąganie Twoich plików", + "systray_download_completed_title": "Pobieranie OnionShare skończone", + "systray_download_completed_message": "Użytkownik ukończył ściąganie Twoich plików", + "systray_download_canceled_title": "Pobieranie OnionShare zostało anulowane", + "systray_download_canceled_message": "Użytkownik anulował pobieranie", + "systray_upload_started_title": "Wysyłanie OnionShare rozpoczęte", + "systray_upload_started_message": "Użytkownik rozpoczął wysyłanie plików na Twój komputer", + "help_local_only": "Nie wykorzystuj sieci Tor (opcja zaawansowana)", + "help_stay_open": "Kontynuuj udostępnianie po pierwszym pobraniu", + "help_shutdown_timeout": "Przestań udostępniać po określonym czasie w sekundach", + "help_stealth": "Korzystaj z weryfikacji klienta (zaawansowane)", + "help_receive": "Odbieraj dane zamiast je wysyłać", + "help_debug": "Zapisz błędy OnionShare do stdout i zapisz błędy sieciowe na dysku", + "help_filename": "Lista plików i folderów do udostępnienia", + "help_config": "Lokalizacja niestandarowego pliku konfiguracyjnego JSON (opcjonalne)", + "gui_drag_and_drop": "Przeciągnij i upuść pliki i foldery\naby je udostępnić", + "gui_add": "Dodaj", "gui_delete": "Usuń", "gui_choose_items": "Wybierz", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", + "gui_share_start_server": "Rozpocznij udostępnianie", + "gui_share_stop_server": "Zatrzymaj udostępnianie", + "gui_share_stop_server_shutdown_timeout": "Zatrzymaj udostępnianie (zostało {}s)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Czas upłynie za {}", + "gui_receive_start_server": "Rozpocznij tryb odbierania", + "gui_receive_stop_server": "Zatrzymaj tryb odbierania", + "gui_receive_stop_server_shutdown_timeout": "Zatrzymaj tryb odbierania (pozostało {}s)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Czas upływa za {}", + "gui_copy_url": "Kopiuj adres załącznika", + "gui_copy_hidservauth": "Kopiuj HidServAuth", + "gui_downloads": "Historia pobierania", + "gui_no_downloads": "Nie pobrano jeszcze niczego", "gui_canceled": "Anulowano", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_copied_url_title": "Skopiowano adres URL OnionShare", + "gui_copied_url": "Adres URL OnionShare został skopiowany do schowka", + "gui_copied_hidservauth_title": "Skopiowano HidServAuth", + "gui_copied_hidservauth": "Linijka HidServAuth została skopiowana do schowka", + "gui_please_wait": "Rozpoczynam... Kliknij, aby zatrzymać.", + "gui_download_upload_progress_complete": "%p%, {0:s} upłynęło.", + "gui_download_upload_progress_starting": "{0:s}, %p% (obliczam)", + "gui_download_upload_progress_eta": "{0:s}, pozostało: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Nie tak szybko", + "gui_share_quit_warning": "Jesteś w trakcie wysyłania plików. Jesteś pewien, że chcesz wyjść z OnionShare?", + "gui_receive_quit_warning": "Odbierasz teraz pliki. Jesteś pewien, że chcesz wyjść z OnionShare?", "gui_quit_warning_quit": "Wyjście", "gui_quit_warning_dont_quit": "Anuluj", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_rate_limit": "Ktoś zbyt często próbował odczytać Twój adres, co może oznaczać, że ktoś próbuje go odgadnąć, zatem OnionShare zatrzymał serwer. Rozpocznij udostępnianie ponownie i wyślij odbiorcy nowy adres aby udostępniać Twoje pliki.", + "zip_progress_bar_format": "Kompresuję: %p%", + "error_stealth_not_supported": "Aby skorzystać z autoryzacji klienta wymagana jest wersja programu Tor 0.2.9.1-alpha lub nowsza, bądź Tor Browser w wersji 6.5 lub nowszej oraz python3-stem w wersji 1.5 lub nowszej.", + "error_ephemeral_not_supported": "OnionShare wymaga programu Tor w wersji 0.2.7.1 lub nowszej oraz python3-stem w wersji 1.4.0 lub nowszej.", "gui_settings_window_title": "Ustawienia", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "gui_settings_whats_this": "Co to jest?", + "gui_settings_stealth_option": "Użyj autoryzacji klienta", + "gui_settings_stealth_hidservauth_string": "Skoro zapisałeś swój klucz prywatny do ponownego użycia, oznacza to, że możesz\nnacisnąć aby skopiować Twój HidServAuth.", "gui_settings_autoupdate_label": "Sprawdź nową wersję", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_option": "Poinformuj mnie, kiedy nowa wersja programu będzie dostępna", + "gui_settings_autoupdate_timestamp": "Ostatnie sprawdzenie aktualizacji: {}", "gui_settings_autoupdate_timestamp_never": "Nigdy", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "Sprawdź, czy nowa wersja programu jest dostępna", "gui_settings_general_label": "Ustawienia ogólne", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", + "gui_settings_sharing_label": "Ustawienia udostępniania", + "gui_settings_close_after_first_download_option": "Przestań udostępniać po pierwszym pobraniu", + "gui_settings_connection_type_label": "W jaki sposób OnionShare powinien połączyć się z siecią Tor?", + "gui_settings_connection_type_bundled_option": "Skorzystaj z wersji Tora udostępnionego wraz z OnionShare", + "gui_settings_connection_type_automatic_option": "Spróbuj automatycznej konfiguracji za pomocą Tor Browser", "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", + "gui_settings_connection_type_socket_file_option": "Połącz z użyciem pliku socket", + "gui_settings_connection_type_test_button": "Sprawdź połączenie z siecią Tor", "gui_settings_control_port_label": "Port sterowania", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "Plik socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Ustawienia autoryzacji sieci Tor", + "gui_settings_authenticate_no_auth_option": "Brak autoryzacji lub autoryzacji ciasteczek", "gui_settings_authenticate_password_option": "Hasło", "gui_settings_password_label": "Hasło", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges": "Wsparcie mostków sieci Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Nie korzystaj z mostków sieci Tor", "gui_settings_tor_bridges_obfs4_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_meek_lite_expensive_warning": "Uwaga: Mostki meek_lite są bardzo kosztowne dla Tor Project.

    Korzystaj z nich tylko wtedy, gdy nie możesz połączyć się bezpośrednio z siecią Tor, poprzez obsf4 albo przez inne normalne mostki.", + "gui_settings_tor_bridges_custom_radio_option": "Użyj niestandardowych mostków", + "gui_settings_tor_bridges_custom_label": "Mostki możesz znaleźć na https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Żadne z dodanych przez Ciebie mostków nie działają.\nZweryfikuj je lub dodaj inne.", "gui_settings_button_save": "Zapisz", "gui_settings_button_cancel": "Anuluj", "gui_settings_button_help": "Pomoc", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", + "settings_error_unknown": "Nie można połączyć się z kontrolerem Tor, ponieważ Twoje ustawienia nie mają sensu.", + "settings_error_automatic": "Nie można połączyć się z kontrolerem Tor. Czy Tor Browser (dostępny na torproject.org) działa w tle?", + "settings_error_socket_port": "Nie można połączyć się z kontrolerem Tor pod adresem {}:{}.", + "settings_error_socket_file": "Nie można połączyć się z kontrolerem Tor używając pliku socket znajdującym się w ścieżce {}.", + "settings_error_auth": "Połączono z {}:{} ale nie można uwierzytelnić. Być może to nie jest kontroler Tor?", + "settings_error_missing_password": "Połączono z kontrolerem Tor ale wymaga on hasła do uwierzytelnienia.", + "settings_error_unreadable_cookie_file": "Połączono z kontrolerem Tor ale hasło może być niepoprawne albo Twój użytkownik nie ma uprawnień do odczytania plików cookie.", "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", + "settings_error_bundled_tor_timeout": "Połączenie się z siecią Tor zajmuje zbyt dużo czasu. Być może nie jesteś połączony z internetem albo masz niedokładny zegar systemowy?", + "settings_error_bundled_tor_broken": "OnionShare nie mógł połączyć się z siecią Tor w tle\n{}", + "settings_test_success": "Połączono z kontrolerem Tor.\n\nWersja Tor: {}\nWsparcie ulotnych serwisów onion: {}.\nWsparcie autoryzacji klienta: {}.\nWsparcie adresów onion nowej generacji: {}.", + "error_tor_protocol_error": "Pojawił się błąd z Tor: {}", + "error_tor_protocol_error_unknown": "Pojawił się nieznany błąd z Tor", + "error_invalid_private_key": "Ten typ klucza prywatnego jest niewspierany", + "connecting_to_tor": "Łączę z siecią Tor", + "update_available": "Nowa wersja programu OnionShare jest dostępna. Naciśnij tutaj aby ją ściągnąć.

    Korzystasz z wersji {} a najnowszą jest {}.", + "update_error_check_error": "Nie można sprawdzić czy są dostępne aktualizacje. Strona programu OnionShare mówi, że ostatnia wersja programu jest nierozpoznawalna '{}'…", "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", @@ -181,5 +181,8 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "Preferowany język", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "Czekam na ukończenie wysyłania", + "gui_add_files": "Dodaj pliki", + "gui_add_folder": "Dodaj foldery" } -- cgit v1.2.3-54-g00ecf From 3ae1e04c0a4498cd55d0fcbbd809686037beac33 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 18:30:13 -0800 Subject: Updated language on key share/receive mode strings to remove the confusing upload and download words --- onionshare_gui/mode/history.py | 32 ++++++++++++++-------------- onionshare_gui/mode/receive_mode/__init__.py | 8 +++---- onionshare_gui/mode/share_mode/__init__.py | 8 +++---- share/locale/en.json | 22 ++++++++++--------- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index e72a3838..3f689bea 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -41,12 +41,12 @@ class HistoryItem(QtWidgets.QWidget): pass -class DownloadHistoryItem(HistoryItem): +class ShareHistoryItem(HistoryItem): """ Download history item, for share mode """ def __init__(self, common, id, total_bytes): - super(DownloadHistoryItem, self).__init__() + super(ShareHistoryItem, self).__init__() self.common = common self.id = id @@ -56,7 +56,7 @@ class DownloadHistoryItem(HistoryItem): self.started_dt = datetime.fromtimestamp(self.started) # Label - self.label = QtWidgets.QLabel(strings._('gui_download_in_progress').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel(strings._('gui_share_mode_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -110,12 +110,12 @@ class DownloadHistoryItem(HistoryItem): self.started) -class UploadHistoryItemFile(QtWidgets.QWidget): +class ReceiveHistoryItemFile(QtWidgets.QWidget): def __init__(self, common, filename): - super(UploadHistoryItemFile, self).__init__() + super(ReceiveHistoryItemFile, self).__init__() self.common = common - self.common.log('UploadHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename)) self.filename = filename self.dir = None @@ -166,10 +166,10 @@ class UploadHistoryItemFile(QtWidgets.QWidget): """ Open the downloads folder, with the file selected, in a cross-platform manner """ - self.common.log('UploadHistoryItemFile', 'open_folder') + self.common.log('ReceiveHistoryItemFile', 'open_folder') if not self.dir: - self.common.log('UploadHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") return abs_filename = os.path.join(self.dir, self.filename) @@ -184,15 +184,15 @@ class UploadHistoryItemFile(QtWidgets.QWidget): # macOS elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + subprocess.call(['open', '-R', abs_filename]) # Windows elif self.common.platform == 'Windows': subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) -class UploadHistoryItem(HistoryItem): +class ReceiveHistoryItem(HistoryItem): def __init__(self, common, id, content_length): - super(UploadHistoryItem, self).__init__() + super(ReceiveHistoryItem, self).__init__() self.common = common self.id = id self.content_length = content_length @@ -259,7 +259,7 @@ class UploadHistoryItem(HistoryItem): for filename in list(data['progress']): # Add a new file if needed if filename not in self.files: - self.files[filename] = UploadHistoryItemFile(self.common, filename) + self.files[filename] = ReceiveHistoryItemFile(self.common, filename) self.files_layout.addWidget(self.files[filename]) # Update the file @@ -280,16 +280,16 @@ class UploadHistoryItem(HistoryItem): 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( + text = strings._('gui_receive_mode_transfer_finished').format( self.started.strftime("%b %d, %I:%M%p") ) else: - text = strings._('gui_upload_finished_range').format( + text = strings._('gui_receive_mode_transfer_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( + text = strings._('gui_receive_mode_transfer_finished_range').format( self.started.strftime("%b %d, %I:%M%p"), self.ended.strftime("%b %d, %I:%M%p") ) @@ -386,7 +386,7 @@ class History(QtWidgets.QWidget): # Header self.header_label = QtWidgets.QLabel(header_text) self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_clear_history')) + clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) clear_button.setFlat(True) clear_button.clicked.connect(self.reset) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index c53f1ea1..0cf33557 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.web import Web -from ..history import History, ToggleHistory, UploadHistoryItem +from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode class ReceiveMode(Mode): @@ -50,8 +50,8 @@ class ReceiveMode(Mode): self.history = History( self.common, QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png'))), - strings._('gui_no_uploads'), - strings._('gui_uploads') + strings._('gui_receive_mode_no_files'), + strings._('gui_all_modes_history') ) self.history.hide() @@ -142,7 +142,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_STARTED event. """ - item = UploadHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 0cc00f92..266aaf06 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -28,7 +28,7 @@ from onionshare.web import Web from .file_selection import FileSelection from .threads import CompressThread from .. import Mode -from ..history import History, ToggleHistory, DownloadHistoryItem +from ..history import History, ToggleHistory, ShareHistoryItem from ...widgets import Alert @@ -75,8 +75,8 @@ class ShareMode(Mode): self.history = History( self.common, QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), - strings._('gui_no_downloads'), - strings._('gui_downloads') + strings._('gui_share_mode_no_files'), + strings._('gui_all_modes_history') ) self.history.hide() @@ -240,7 +240,7 @@ class ShareMode(Mode): else: filesize = self.web.share_mode.download_filesize - item = DownloadHistoryItem(self.common, event["data"]["id"], filesize) + item = ShareHistoryItem(self.common, event["data"]["id"], filesize) self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 diff --git a/share/locale/en.json b/share/locale/en.json index 44eff150..45339333 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -48,8 +48,6 @@ "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer ends at {}", "gui_copy_url": "Copy Address", "gui_copy_hidservauth": "Copy HidServAuth", - "gui_downloads": "Download History", - "gui_no_downloads": "No Downloads Yet", "gui_canceled": "Canceled", "gui_copied_url_title": "Copied OnionShare Address", "gui_copied_url": "OnionShare address copied to clipboard", @@ -175,14 +173,18 @@ "systray_page_loaded_title": "OnionShare Page Loaded", "systray_download_page_loaded_message": "A user loaded the download page", "systray_upload_page_loaded_message": "A user loaded the upload page", - "gui_uploads": "Upload History", - "gui_no_uploads": "No Uploads Yet", - "gui_clear_history": "Clear All", - "gui_upload_in_progress": "Upload Started {}", - "gui_upload_finished_range": "Uploaded {} to {}", - "gui_upload_finished": "Uploaded {}", - "gui_download_in_progress": "Download Started {}", "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." + "gui_settings_language_changed_notice": "Restart OnionShare for your change in language to take effect.", + + "gui_all_modes_history": "History", + "gui_all_modes_clear_history": "Clear All", + + "gui_share_mode_no_files": "No Files Sent Yet", + "gui_share_mode_transfer_started": "Started {}", + + "gui_receive_mode_no_files": "No Files Received Yet", + "gui_receive_mode_transfer_started": "Started {}", + "gui_receive_mode_transfer_finished_range": "Started {}, finished {}", + "gui_receive_mode_transfer_finished": "Finished {}" } -- cgit v1.2.3-54-g00ecf From 862a0dc067f38d700ec2339633a0701ac6b271d1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 19:00:41 -0800 Subject: Rename images to remove upload/download references, and update more strings --- onionshare_gui/mode/history.py | 60 +++++++++++++++----------- onionshare_gui/mode/receive_mode/__init__.py | 6 +-- onionshare_gui/mode/share_mode/__init__.py | 6 +-- share/images/downloads.png | Bin 2120 -> 0 bytes share/images/downloads_toggle.png | Bin 380 -> 0 bytes share/images/downloads_toggle_selected.png | Bin 468 -> 0 bytes share/images/downloads_transparent.png | Bin 2138 -> 0 bytes share/images/receive_icon.png | Bin 0 -> 2120 bytes share/images/receive_icon_toggle.png | Bin 0 -> 380 bytes share/images/receive_icon_toggle_selected.png | Bin 0 -> 468 bytes share/images/receive_icon_transparent.png | Bin 0 -> 2138 bytes share/images/share_icon.png | Bin 0 -> 2076 bytes share/images/share_icon_toggle.png | Bin 0 -> 389 bytes share/images/share_icon_toggle_selected.png | Bin 0 -> 473 bytes share/images/share_icon_transparent.png | Bin 0 -> 2096 bytes share/images/uploads.png | Bin 2076 -> 0 bytes share/images/uploads_toggle.png | Bin 389 -> 0 bytes share/images/uploads_toggle_selected.png | Bin 473 -> 0 bytes share/images/uploads_transparent.png | Bin 2096 -> 0 bytes share/locale/en.json | 17 +++----- 20 files changed, 49 insertions(+), 40 deletions(-) delete mode 100644 share/images/downloads.png delete mode 100644 share/images/downloads_toggle.png delete mode 100644 share/images/downloads_toggle_selected.png delete mode 100644 share/images/downloads_transparent.png create mode 100644 share/images/receive_icon.png create mode 100644 share/images/receive_icon_toggle.png create mode 100644 share/images/receive_icon_toggle_selected.png create mode 100644 share/images/receive_icon_transparent.png create mode 100644 share/images/share_icon.png create mode 100644 share/images/share_icon_toggle.png create mode 100644 share/images/share_icon_toggle_selected.png create mode 100644 share/images/share_icon_transparent.png delete mode 100644 share/images/uploads.png delete mode 100644 share/images/uploads_toggle.png delete mode 100644 share/images/uploads_toggle_selected.png delete mode 100644 share/images/uploads_transparent.png diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 3f689bea..44ee293f 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -40,6 +40,30 @@ class HistoryItem(QtWidgets.QWidget): def cancel(self): pass + def get_finished_label_text(self, started_ts): + """ + When an item finishes, returns a string displaying the start/end datetime range. + started is a datetime object. + """ + started = datetime.fromtimestamp(started_ts) + ended = datetime.now() + if started.year == ended.year and started.month == ended.month and started.day == ended.day: + if started.hour == ended.hour and started.minute == ended.minute: + text = strings._('gui_all_modes_transfer_finished').format( + started.strftime("%b %d, %I:%M%p") + ) + else: + text = strings._('gui_all_modes_transfer_finished_range').format( + started.strftime("%b %d, %I:%M%p"), + ended.strftime("%I:%M%p") + ) + else: + text = strings._('gui_all_modes_transfer_finished_range').format( + started.strftime("%b %d, %I:%M%p"), + ended.strftime("%b %d, %I:%M%p") + ) + return text + class ShareHistoryItem(HistoryItem): """ @@ -56,7 +80,7 @@ class ShareHistoryItem(HistoryItem): self.started_dt = datetime.fromtimestamp(self.started) # Label - self.label = QtWidgets.QLabel(strings._('gui_share_mode_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -83,18 +107,22 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_download_upload_progress_complete').format( + pb_fmt = strings._('gui_all_modes_progress_complete').format( self.common.format_seconds(time.time() - self.started)) + + # Change the label + self.label.setText(self.get_finished_label_text(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( + pb_fmt = strings._('gui_all_modes_progress_starting').format( self.common.human_readable_filesize(downloaded_bytes)) else: - pb_fmt = strings._('gui_download_upload_progress_eta').format( + pb_fmt = strings._('gui_all_modes_progress_eta').format( self.common.human_readable_filesize(downloaded_bytes), self.estimated_time_remaining) @@ -199,7 +227,7 @@ class ReceiveHistoryItem(HistoryItem): self.started = datetime.now() # Label - self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -244,14 +272,14 @@ class ReceiveHistoryItem(HistoryItem): elapsed = datetime.now() - self.started if elapsed.seconds < 10: - pb_fmt = strings._('gui_download_upload_progress_starting').format( + pb_fmt = strings._('gui_all_modes_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( + pb_fmt = strings._('gui_all_modes_progress_eta').format( self.common.human_readable_filesize(total_uploaded_bytes), estimated_time_remaining) @@ -277,23 +305,7 @@ class ReceiveHistoryItem(HistoryItem): 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_receive_mode_transfer_finished').format( - self.started.strftime("%b %d, %I:%M%p") - ) - else: - text = strings._('gui_receive_mode_transfer_finished_range').format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%I:%M%p") - ) - else: - text = strings._('gui_receive_mode_transfer_finished_range').format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%b %d, %I:%M%p") - ) - self.label.setText(text) + self.label.setText(self.get_finished_label_text(self.started)) class HistoryItemList(QtWidgets.QScrollArea): diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 0cf33557..2ebf1feb 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -49,7 +49,7 @@ class ReceiveMode(Mode): # Upload history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/uploads_transparent.png'))), + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))), strings._('gui_receive_mode_no_files'), strings._('gui_all_modes_history') ) @@ -58,8 +58,8 @@ class ReceiveMode(Mode): # Toggle history self.toggle_history = ToggleHistory( self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/uploads_toggle_selected.png')) + QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png')) ) # Receive mode warning diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 266aaf06..f7150c1e 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -74,7 +74,7 @@ class ShareMode(Mode): # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/downloads_transparent.png'))), + QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), strings._('gui_share_mode_no_files'), strings._('gui_all_modes_history') ) @@ -87,8 +87,8 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/downloads_toggle_selected.png')) + QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), + QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) ) # Top bar diff --git a/share/images/downloads.png b/share/images/downloads.png deleted file mode 100644 index ad879b6e..00000000 Binary files a/share/images/downloads.png and /dev/null differ diff --git a/share/images/downloads_toggle.png b/share/images/downloads_toggle.png deleted file mode 100644 index 846ececb..00000000 Binary files a/share/images/downloads_toggle.png and /dev/null differ diff --git a/share/images/downloads_toggle_selected.png b/share/images/downloads_toggle_selected.png deleted file mode 100644 index 127ce208..00000000 Binary files a/share/images/downloads_toggle_selected.png and /dev/null differ diff --git a/share/images/downloads_transparent.png b/share/images/downloads_transparent.png deleted file mode 100644 index 99207097..00000000 Binary files a/share/images/downloads_transparent.png and /dev/null differ diff --git a/share/images/receive_icon.png b/share/images/receive_icon.png new file mode 100644 index 00000000..ad879b6e Binary files /dev/null and b/share/images/receive_icon.png differ diff --git a/share/images/receive_icon_toggle.png b/share/images/receive_icon_toggle.png new file mode 100644 index 00000000..846ececb Binary files /dev/null and b/share/images/receive_icon_toggle.png differ diff --git a/share/images/receive_icon_toggle_selected.png b/share/images/receive_icon_toggle_selected.png new file mode 100644 index 00000000..127ce208 Binary files /dev/null and b/share/images/receive_icon_toggle_selected.png differ diff --git a/share/images/receive_icon_transparent.png b/share/images/receive_icon_transparent.png new file mode 100644 index 00000000..99207097 Binary files /dev/null and b/share/images/receive_icon_transparent.png differ diff --git a/share/images/share_icon.png b/share/images/share_icon.png new file mode 100644 index 00000000..cd9bd98e Binary files /dev/null and b/share/images/share_icon.png differ diff --git a/share/images/share_icon_toggle.png b/share/images/share_icon_toggle.png new file mode 100644 index 00000000..87303c9f Binary files /dev/null and b/share/images/share_icon_toggle.png differ diff --git a/share/images/share_icon_toggle_selected.png b/share/images/share_icon_toggle_selected.png new file mode 100644 index 00000000..0ba52cff Binary files /dev/null and b/share/images/share_icon_toggle_selected.png differ diff --git a/share/images/share_icon_transparent.png b/share/images/share_icon_transparent.png new file mode 100644 index 00000000..3648c3fb Binary files /dev/null and b/share/images/share_icon_transparent.png differ diff --git a/share/images/uploads.png b/share/images/uploads.png deleted file mode 100644 index cd9bd98e..00000000 Binary files a/share/images/uploads.png and /dev/null differ diff --git a/share/images/uploads_toggle.png b/share/images/uploads_toggle.png deleted file mode 100644 index 87303c9f..00000000 Binary files a/share/images/uploads_toggle.png and /dev/null differ diff --git a/share/images/uploads_toggle_selected.png b/share/images/uploads_toggle_selected.png deleted file mode 100644 index 0ba52cff..00000000 Binary files a/share/images/uploads_toggle_selected.png and /dev/null differ diff --git a/share/images/uploads_transparent.png b/share/images/uploads_transparent.png deleted file mode 100644 index 3648c3fb..00000000 Binary files a/share/images/uploads_transparent.png and /dev/null differ diff --git a/share/locale/en.json b/share/locale/en.json index 45339333..ce3cf87a 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -54,9 +54,6 @@ "gui_copied_hidservauth_title": "Copied HidServAuth", "gui_copied_hidservauth": "HidServAuth line copied to clipboard", "gui_please_wait": "Starting… Click to cancel.", - "gui_download_upload_progress_complete": "%p%, {0:s} elapsed.", - "gui_download_upload_progress_starting": "{0:s}, %p% (calculating)", - "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Not so fast", "gui_share_quit_warning": "You're in the process of sending files. Are you sure you want to quit OnionShare?", @@ -179,12 +176,12 @@ "gui_all_modes_history": "History", "gui_all_modes_clear_history": "Clear All", - + "gui_all_modes_transfer_started": "Started {}", + "gui_all_modes_transfer_finished_range": "Transferred {} - {}", + "gui_all_modes_transfer_finished": "Transferred {}", + "gui_all_modes_progress_complete": "%p%, {0:s} elapsed.", + "gui_all_modes_progress_starting": "{0:s}, %p% (calculating)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", "gui_share_mode_no_files": "No Files Sent Yet", - "gui_share_mode_transfer_started": "Started {}", - - "gui_receive_mode_no_files": "No Files Received Yet", - "gui_receive_mode_transfer_started": "Started {}", - "gui_receive_mode_transfer_finished_range": "Started {}, finished {}", - "gui_receive_mode_transfer_finished": "Finished {}" + "gui_receive_mode_no_files": "No Files Received Yet" } -- cgit v1.2.3-54-g00ecf From a0c3a276ec4870b083bec829fcafab222ba51e68 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 19:33:36 -0800 Subject: Fix bug in HistoryItem.get_finished_label_text --- onionshare_gui/mode/history.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 44ee293f..bb419ec7 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -40,12 +40,11 @@ class HistoryItem(QtWidgets.QWidget): def cancel(self): pass - def get_finished_label_text(self, started_ts): + def get_finished_label_text(self, started): """ When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ - started = datetime.fromtimestamp(started_ts) ended = datetime.now() if started.year == ended.year and started.month == ended.month and started.day == ended.day: if started.hour == ended.hour and started.minute == ended.minute: @@ -111,7 +110,7 @@ class ShareHistoryItem(HistoryItem): self.common.format_seconds(time.time() - self.started)) # Change the label - self.label.setText(self.get_finished_label_text(self.started)) + self.label.setText(self.get_finished_label_text(self.started_dt)) else: elapsed = time.time() - self.started -- cgit v1.2.3-54-g00ecf From 3bbff7e85ab31e8fc593b74ec723b493f0c63936 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 20:43:25 -0800 Subject: Finish removing web event REQUEST_CLOSE_SERVER, which used to be a receive mode feature to allow the Tor Browser user to close the server, but we removed --- onionshare/web/web.py | 9 ++++----- onionshare_gui/mode/__init__.py | 6 ------ onionshare_gui/mode/receive_mode/__init__.py | 9 +-------- onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 3 --- 5 files changed, 6 insertions(+), 23 deletions(-) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 0f156941..df838df7 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -35,11 +35,10 @@ class Web(object): REQUEST_OTHER = 3 REQUEST_CANCELED = 4 REQUEST_RATE_LIMIT = 5 - REQUEST_CLOSE_SERVER = 6 - REQUEST_UPLOAD_FILE_RENAMED = 7 - REQUEST_UPLOAD_SET_DIR = 8 - REQUEST_UPLOAD_FINISHED = 9 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 10 + REQUEST_UPLOAD_FILE_RENAMED = 6 + REQUEST_UPLOAD_SET_DIR = 7 + REQUEST_UPLOAD_FINISHED = 8 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 def __init__(self, common, is_gui, mode='share'): self.common = common diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 5110289f..edc1777d 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -312,12 +312,6 @@ class Mode(QtWidgets.QWidget): """ pass - def handle_request_close_server(self, event): - """ - Handle REQUEST_CLOSE_SERVER event. - """ - pass - def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 2ebf1feb..62f96924 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -103,7 +103,7 @@ class ReceiveMode(Mode): return True # An upload is probably still running - hold off on stopping the share, but block new shares. else: - self.server_status_label.setText(strings._('timeout_upload_still_running')) + self.server_status_label.setText(strings._('gui_receive_mode_timeout_waiting')) self.web.receive_mode.can_upload = False return False @@ -159,13 +159,6 @@ class ReceiveMode(Mode): 'progress': event["data"]["progress"] }) - def handle_request_close_server(self, event): - """ - Handle REQUEST_CLOSE_SERVER event. - """ - self.stop_server() - self.system_tray.showMessage(strings._('systray_close_server_title'), strings._('systray_close_server_message')) - def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index f7150c1e..5dd2315e 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -138,7 +138,7 @@ class ShareMode(Mode): return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('timeout_download_still_running')) + self.server_status_label.setText(strings._('gui_share_mode_timeout_waiting')) return False def start_server_custom(self): diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index eab3261e..3d5f5f0a 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -384,9 +384,6 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_CANCELED: mode.handle_request_canceled(event) - elif event["type"] == Web.REQUEST_CLOSE_SERVER: - mode.handle_request_close_server(event) - elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event) -- cgit v1.2.3-54-g00ecf From b8f0d789545f8bd34b4bb963c2e4eb6d1d1b7aec Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 20:56:09 -0800 Subject: Change many more strings to stop using 'download'/'upload' language --- onionshare_gui/mode/receive_mode/__init__.py | 4 +-- onionshare_gui/mode/share_mode/__init__.py | 8 +++--- share/locale/en.json | 40 ++++++++++++---------------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 62f96924..90a1f731 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -136,7 +136,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_upload_page_loaded_message')) + self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) def handle_request_started(self, event): """ @@ -148,7 +148,7 @@ class ReceiveMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_upload_started_title'), strings._('systray_upload_started_message')) + self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message')) def handle_request_progress(self, event): """ diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 5dd2315e..d0806cfa 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -229,7 +229,7 @@ class ShareMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_download_page_loaded_message')) + self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) def handle_request_started(self, event): """ @@ -246,7 +246,7 @@ class ShareMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_download_started_title'), strings._('systray_download_started_message')) + self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message')) def handle_request_progress(self, event): """ @@ -256,7 +256,7 @@ class ShareMode(Mode): # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_download_completed_title'), strings._('systray_download_completed_message')) + self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message')) # Update completed and in progress labels self.history.completed_count += 1 @@ -284,7 +284,7 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_download_canceled_title'), strings._('systray_download_canceled_message')) + self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message')) def on_reload_settings(self): """ diff --git a/share/locale/en.json b/share/locale/en.json index ce3cf87a..8bcc738b 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -11,21 +11,10 @@ "no_available_port": "Could not find an available port to start the onion service", "other_page_loaded": "Address loaded", "close_on_timeout": "Stopped because auto-stop timer ran out", - "closing_automatically": "Stopped because download finished", - "timeout_download_still_running": "Waiting for download to complete", - "timeout_upload_still_running": "Waiting for upload to complete", + "closing_automatically": "Stopped because transfer is complete", "large_filesize": "Warning: Sending a large share could take hours", - "systray_menu_exit": "Quit", - "systray_download_started_title": "OnionShare Download Started", - "systray_download_started_message": "A user started downloading your files", - "systray_download_completed_title": "OnionShare Download Finished", - "systray_download_completed_message": "The user finished downloading your files", - "systray_download_canceled_title": "OnionShare Download Canceled", - "systray_download_canceled_message": "The user canceled the download", - "systray_upload_started_title": "OnionShare Upload Started", - "systray_upload_started_message": "A user started uploading files to your computer", "help_local_only": "Don't use Tor (only for development)", - "help_stay_open": "Keep sharing after first download", + "help_stay_open": "Continue sharing after files have been sent", "help_shutdown_timeout": "Stop sharing after a given amount of seconds", "help_stealth": "Use client authorization (advanced)", "help_receive": "Receive shares instead of sending them", @@ -75,7 +64,7 @@ "gui_settings_autoupdate_check_button": "Check for New Version", "gui_settings_general_label": "General settings", "gui_settings_sharing_label": "Sharing settings", - "gui_settings_close_after_first_download_option": "Stop sharing after first download", + "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", "gui_settings_connection_type_bundled_option": "Use the Tor version built into OnionShare", "gui_settings_connection_type_automatic_option": "Attempt auto-configuration with Tor Browser", @@ -151,8 +140,6 @@ "gui_file_info_single": "{} file, {}", "history_in_progress_tooltip": "{} in progress", "history_completed_tooltip": "{} completed", - "info_in_progress_uploads_tooltip": "{} upload(s) in progress", - "info_completed_uploads_tooltip": "{} upload(s) completed", "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", @@ -165,15 +152,20 @@ "gui_settings_downloads_label": "Save files to", "gui_settings_downloads_button": "Browse", "gui_settings_public_mode_checkbox": "Public mode", - "systray_close_server_title": "OnionShare Server Closed", - "systray_close_server_message": "A user closed the server", - "systray_page_loaded_title": "OnionShare Page Loaded", - "systray_download_page_loaded_message": "A user loaded the download page", - "systray_upload_page_loaded_message": "A user loaded the upload page", "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.", - + "systray_menu_exit": "Quit", + "systray_page_loaded_title": "Page Loaded", + "systray_page_loaded_message": "OnionShare address loaded", + "systray_share_started_title": "Sharing Started", + "systray_share_started_message": "Starting to send files to someone", + "systray_share_completed_title": "Sharing Complete", + "systray_share_completed_message": "Finished sending files", + "systray_share_canceled_title": "Sharing Canceled", + "systray_share_canceled_message": "Someone canceled receiving your files", + "systray_receive_started_title": "Receiving Started", + "systray_receive_started_message": "Someone is sending files to you", "gui_all_modes_history": "History", "gui_all_modes_clear_history": "Clear All", "gui_all_modes_transfer_started": "Started {}", @@ -183,5 +175,7 @@ "gui_all_modes_progress_starting": "{0:s}, %p% (calculating)", "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", "gui_share_mode_no_files": "No Files Sent Yet", - "gui_receive_mode_no_files": "No Files Received Yet" + "gui_share_mode_timeout_waiting": "Waiting to finish sending", + "gui_receive_mode_no_files": "No Files Received Yet", + "gui_receive_mode_timeout_waiting": "Waiting to finish receiving" } -- cgit v1.2.3-54-g00ecf From 89ccf0306b207356f5f3e80c5d173da5b5862a08 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 Jan 2019 21:18:20 -0800 Subject: Weblate merge (#871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Added translation using Weblate (Hebrew) * Added translation using Weblate (Japanese) * Added translation using Weblate (Shona) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ --- share/locale/ar.json | 41 ++++---- share/locale/bg.json | 2 +- share/locale/cs.json | 33 +++++-- share/locale/da.json | 9 +- share/locale/de.json | 54 ++++++----- share/locale/es.json | 13 ++- share/locale/fa.json | 107 +++++++++++---------- share/locale/fr.json | 14 +-- share/locale/he.json | 188 ++++++++++++++++++++++++++++++++++++ share/locale/it.json | 223 ++++++++++++++++++++++++++++++------------ share/locale/ja.json | 188 ++++++++++++++++++++++++++++++++++++ share/locale/ko.json | 58 +++++------ share/locale/nl.json | 71 +++++++------- share/locale/no.json | 7 +- share/locale/pl.json | 205 ++++++++++++++++++++------------------- share/locale/sn.json | 188 ++++++++++++++++++++++++++++++++++++ share/locale/sv.json | 267 ++++++++++++++++++++++++++------------------------- 17 files changed, 1190 insertions(+), 478 deletions(-) create mode 100644 share/locale/he.json create mode 100644 share/locale/ja.json create mode 100644 share/locale/sn.json diff --git a/share/locale/ar.json b/share/locale/ar.json index 92987bd7..5924eb5d 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -1,31 +1,31 @@ { "config_onion_service": "تثبيت خدمة onion على المنفذ {0:d}.", - "preparing_files": "ضغط الملفات.", + "preparing_files": "جاري ضغط الملفات.", "give_this_url": "أعط هذا العنوان للمتلقي:", - "give_this_url_stealth": "", - "give_this_url_receive": "إعط هذا العنوان للمُرسِل:", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "give_this_url_stealth": "أعط العنوان التالى و السطر الذى يحتوى على (HidServAuth) للمتلقى:", + "give_this_url_receive": "اعط هذا العنوان للمرسل:", + "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", + "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف الخادم", + "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", + "not_a_readable_file": "{0:s} ملف لا يمكن قراءته.", + "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", + "other_page_loaded": "تم تحميل العنوان", "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "closing_automatically": "توقف بسبب انتهاء التحميل", + "timeout_download_still_running": "انتظار اكتمال التحميل", + "large_filesize": "تحذير: ارسال مشاركة كبيرة قد يستغرق ساعات", "systray_menu_exit": "خروج", "systray_download_started_title": "", "systray_download_started_message": "", "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "المستخدم الغاء التحميل", - "systray_upload_started_title": "onionshare تحميل بدأت", - "systray_upload_started_message": "بدأ المستخدم تحميل الملفات إلى جهاز الكمبيوتر الخاص بك", + "systray_download_completed_message": "اكمل المستخدم تحميل الملفات", + "systray_download_canceled_title": "تم الغاء التحميل", + "systray_download_canceled_message": "الغى المستخدم التحميل", + "systray_upload_started_title": "بدأ الرفع", + "systray_upload_started_message": "بدأ مستخدم رفع ملفات الى حاسوبك", "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", + "help_stay_open": "استمر في المشاركة بعد اول تحميل", + "help_shutdown_timeout": "أوقف المشاركة بعد ثواني محددة", "help_stealth": "", "help_receive": "", "help_debug": "", @@ -181,5 +181,6 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "انتظار اكتمال الرفع" } diff --git a/share/locale/bg.json b/share/locale/bg.json index e8a8f3e5..256f27e0 100644 --- a/share/locale/bg.json +++ b/share/locale/bg.json @@ -8,7 +8,7 @@ "ctrlc_to_stop": "Натиснете Ctrl+C, за да спрете сървъра", "not_a_file": "{0: s) не е валиден документ.", "not_a_readable_file": "{0:s) не е четаем файл.", - "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана.", + "no_available_port": "Свободен порт не бе намерен, за да може onion услугата да бъде стартирана", "other_page_loaded": "Адресът е зареден", "close_on_timeout": "Спряно, защото автоматично спиращият таймер приключи", "closing_automatically": "Спряно, защото свалянето приключи", diff --git a/share/locale/cs.json b/share/locale/cs.json index 74eb8ea3..3eb03198 100644 --- a/share/locale/cs.json +++ b/share/locale/cs.json @@ -1,10 +1,10 @@ { - "config_onion_service": "Nastavuji onion service na portu {0:d}.", - "preparing_files": "Připravuji soubory ke sdílení.", + "config_onion_service": "Nastavuji onion požadavky na portu {0:d}.", + "preparing_files": "Probíhá komprese souborů.", "give_this_url": "Dejte tuto URL osobě, které dané soubory posíláte:", - "give_this_url_stealth": "Give this URL and HidServAuth line to the person you're sending the file to:", - "ctrlc_to_stop": "Stiskněte Ctrl-C pro zastavení serveru", - "not_a_file": "{0:s} není soubor.", + "give_this_url_stealth": "Sděl příjemci tuto URL a HidServAuth:", + "ctrlc_to_stop": "Stiskněte Ctrl+C pro zastavení serveru", + "not_a_file": "{0:s} není platný soubor.", "other_page_loaded": "URL loaded", "closing_automatically": "Zastavuji automaticky, protože stahování skončilo", "large_filesize": "Varování: Posílání velkých souborů může trvat hodiny", @@ -21,7 +21,7 @@ "gui_share_stop_server": "Zastavit sdílení", "gui_copy_url": "Kopírovat URL", "gui_copy_hidservauth": "Kopírovat HidServAuth", - "gui_downloads": "Stahování:", + "gui_downloads": "Historie stahování", "gui_canceled": "Zrušeno", "gui_copied_url": "URL zkopírováno do schránky", "gui_copied_hidservauth": "Copied HidServAuth line to clipboard", @@ -59,5 +59,24 @@ "settings_error_missing_password": "Připojen k ovladači Toru, ale vyžaduje heslo pro autentizaci.", "settings_error_unreadable_cookie_file": "Připojen k ovladači Toru, ale nejde se autentizovat, protože heslo je možná špatné a váš uživatel nemá povolení číst soubor cookie.", "settings_test_success": "Congratulations, OnionShare can connect to the Tor controller.\n\nTor version: {}\nSupports ephemeral onion services: {}\nSupports stealth onion services: {}", - "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work." + "error_tor_protocol_error": "Error talking to the Tor controller.\nIf you're using Whonix, check out https://www.whonix.org/wiki/onionshare to make OnionShare work.", + "give_this_url_receive": "Sděl tuto adresu odesilateli:", + "give_this_url_receive_stealth": "Sdělte tuto adresu a HidServAuth odesilateli:", + "no_available_port": "Port potřebný ke spuštění služeb onion nelze nalézt", + "not_a_readable_file": "{0:s} soubor není čitelný.", + "timeout_download_still_running": "Čeká se na konec stahování", + "systray_menu_exit": "Ukončit", + "systray_download_started_title": "Stahování pomocí OnionShare začalo", + "systray_download_started_message": "Někdo stahuje vaše soubory", + "systray_download_completed_title": "Stahování pomocí OnionShare skončilo", + "systray_download_completed_message": "Uživatel dokončil stahování vašich souborů", + "systray_download_canceled_title": "Stahování pomocí OnionShare bylo zrušeno", + "systray_download_canceled_message": "Uživatel přerušil stahování souboru", + "systray_upload_started_title": "Začalo nahrávání pomocí OnionShare", + "systray_upload_started_message": "Někdo právě začal nahrávat soubory na váš počítač", + "gui_share_stop_server_shutdown_timeout": "Zastavit sdílení ({}s zbývá)", + "gui_receive_start_server": "Spustit mód přijímání", + "gui_receive_stop_server": "Zastavit přijímání", + "gui_receive_stop_server_shutdown_timeout": "Zastavit mód přijímání ({}s zbývá)", + "gui_copied_hidservauth_title": "Zkopírovaný HidServAuth token" } diff --git a/share/locale/da.json b/share/locale/da.json index b759cdc4..fdb456e0 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -51,7 +51,7 @@ "error_stealth_not_supported": "For at bruge klientautentifikation skal du have mindst Tor 0.2.9.1-alpha (eller Tor Browser 6.5) og python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", - "gui_settings_stealth_option": "Brug klientautentifikation (forældet)", + "gui_settings_stealth_option": "Brug klientautentifikation", "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu\nkan klikke for at kopiere din HidServAuth.", "gui_settings_autoupdate_label": "Søg efter ny version", "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", @@ -111,7 +111,7 @@ "gui_server_started_after_timeout": "Timeren med autostop løb ud inden serveren startede.\nOpret venligst en ny deling.", "gui_server_timeout_expired": "Timeren med autostop er allerede løbet ud.\nOpdater den venligst for at starte deling.", "share_via_onionshare": "Del via OnionShare", - "gui_save_private_key_checkbox": "Brug en vedvarende adresse (udgået)", + "gui_save_private_key_checkbox": "Brug en vedvarende adresse", "gui_copied_url_title": "Kopierede OnionShare-adresse", "gui_copied_hidservauth_title": "Kopierede HidServAuth", "gui_quit_title": "Klap lige hesten", @@ -185,5 +185,8 @@ "gui_receive_mode_warning": "Modtagetilstand lader folk uploade filer til din computer.

    Nogle filer kan potentielt tage kontrol over din computer hvis du åbner dem. Åbn kun ting fra folk du har tillid til, eller hvis du ved hvad du har gang i.", "receive_mode_upload_starting": "Upload med samlet størrelse på {} starter", "gui_open_folder_error_nautilus": "Kan ikke åbne mappe fordi nautilus ikke er tilgængelig. Filen er her: {}", - "timeout_upload_still_running": "Venter på at upload skal blive færdig" + "timeout_upload_still_running": "Venter på at upload skal blive færdig", + "gui_add_files": "Tilføj filer", + "gui_add_folder": "Tilføj mappe", + "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste" } diff --git a/share/locale/de.json b/share/locale/de.json index 3aae1951..8e9bd634 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -1,15 +1,15 @@ { - "preparing_files": "Dateien werden vorbereitet.", + "preparing_files": "Dateien werden komprimiert.", "give_this_url": "Gib diese URL an den Empfänger:", "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", - "not_a_file": "{0:s} ist keine Datei.", + "not_a_file": "{0:s} ist keine gültige Datei.", "other_page_loaded": "URL geladen", "closing_automatically": "Gestoppt, da der Download beendet wurde", "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", - "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler aus die Festplatte", - "help_filename": "Liste der zu teilenden Dateien oder Verzeichnisse", + "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler auf die Festplatte", + "help_filename": "Liste der zu teilenden Dateien oder Ordner", "gui_drag_and_drop": "Dateien und Ordner hierher ziehen\num sie zu teilen", "gui_add": "Hinzufügen", "gui_delete": "Löschen", @@ -28,11 +28,11 @@ "gui_settings_button_cancel": "Abbrechen", "gui_settings_button_help": "Hilfe", "gui_settings_shutdown_timeout": "Stoppe den Server bei:", - "systray_download_started_title": "OnionShareDownload begonnen", - "systray_download_started_message": "Ein Nutzer hat begonnen deine Dateien herunterzuladen", + "systray_download_started_title": "OnionShare Download begonnen", + "systray_download_started_message": "Ein Nutzer hat begonnen, deine Dateien herunterzuladen", "systray_download_completed_title": "OnionShare Download beendet", "systray_download_completed_message": "Der Benutzer hat deine Dateien heruntergeladen", - "systray_download_canceled_title": "OnionShareDownload abgebrochen", + "systray_download_canceled_title": "OnionShare Download abgebrochen", "systray_download_canceled_message": "Der Benutzer hat den Download abgebrochen", "gui_copy_hidservauth": "HidServAuth kopieren", "gui_canceled": "Abgebrochen", @@ -49,10 +49,10 @@ "give_this_url_receive": "Gib diese URL dem Sender:", "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", "not_a_readable_file": "{0:s} kann nicht gelesen werden.", - "no_available_port": "Konnte keinen freien Port finden, um den Onionservice zu starten", + "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onionservice zu starten", "close_on_timeout": "Wegen Zeitablaufs gestoppt", - "systray_upload_started_title": "OnionShare Upload gestartet", - "systray_upload_started_message": "Ein Nutzer hat begonnen Dateien auf deinen Computer zu laden", + "systray_upload_started_title": "OnionShare Upload wurde gestartet", + "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", "help_receive": "Empfange Dateien anstatt sie zu senden", "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", @@ -82,7 +82,7 @@ "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {})", "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", "gui_no_downloads": "Bisher keine Downloads", - "gui_copied_url_title": "OnionShareadresse kopiert", + "gui_copied_url_title": "OnionShare-Adresse kopiert", "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert", "gui_download_upload_progress_complete": "%p%, {0:s} vergangen.", "gui_download_upload_progress_starting": "{0:s}, %p% (berechne)", @@ -93,16 +93,16 @@ "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", "zip_progress_bar_format": "Komprimiere: %p%", - "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (or Tor Browser 6.5) und python3-stem 1.5.0.", + "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", - "gui_settings_stealth_option": "Nutze Klientauthorisierung (Alt)", + "gui_settings_stealth_option": "Nutze Klientauthorisierung", "gui_settings_autoupdate_label": "Suche nach einer neueren Version", "gui_settings_autoupdate_option": "Benachrichtige mich, wenn eine neuere Version verfügbar ist", "gui_settings_autoupdate_check_button": "Suche nach neuerer Version", "gui_settings_general_label": "Allgemeine Einstellungen", "gui_settings_sharing_label": "Freigabe-Einstellungen", - "gui_settings_connection_type_automatic_option": "Versuche auto-konfiguartion mittels des Tor Browsers", + "gui_settings_connection_type_automatic_option": "Versuche Autokonfiguration mittels Tor Browser", "gui_settings_connection_type_test_button": "Teste Verbindung zum Tornetzwerk", "gui_settings_authenticate_label": "Autentifizierungseinstellungen für Tor", "gui_settings_tor_bridges": "Unterstützung für Tor Bridges", @@ -112,11 +112,11 @@ "settings_error_automatic": "Kann nicht zum Tor controller verbinden. Läuft der Tor Browser (zum Download unter torproject.org) im Hintergrund?", "settings_error_socket_port": "Kann unter {}:{} nicht zum Tor controller verbinden.", "settings_error_unreadable_cookie_file": "Verbindung zum Tor controller hergestellt, aber dein Passwort ist falsch oder dein Nutzer darf die Cookiedatei nicht lesen.", - "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird nicht nutzen.", + "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird, nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", - "settings_error_bundled_tor_broken": "OnionShare konnte in Hintergrund nicht mit Tor verbinden:\n{}", - "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Klient-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", - "error_tor_protocol_error": "Es gab eine Fehler mit Tor: {}", + "settings_error_bundled_tor_broken": "OnionShare konnte im Hintergrund nicht mit Tor verbinden:\n{}", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", + "error_tor_protocol_error": "Es gab einen Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", "update_available": "Es gibt eine neue Version von OnionShare. Klicke hier um sie herunterzuladen.

    Du benutzt {} und die neueste Version ist {}.", @@ -129,7 +129,7 @@ "gui_tor_connection_canceled": "Konnte keine Verbindung zu Tor herstellen.\n\nStelle sicher, dass du mit dem Internet verbunden bist, öffne OnionShare erneut und richte die Verbindung zu Tor ein.", "share_via_onionshare": "Teile es per OnionShare", "gui_use_legacy_v2_onions_checkbox": "Nutze das alte Adressformat", - "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse (alt)", + "gui_save_private_key_checkbox": "Nutze eine gleichbleibende Adresse", "gui_share_url_description": "Jeder mit dieser OnionShareAdresse kann deine Dateien mit dem Tor Browser herunterladen: ", "gui_receive_url_description": "Jeder mit dieser OnionShareAdresse kann mit dem Tor Browser Dateien auf deinen Computer hochladen: ", "gui_url_label_persistent": "Dieser Server wird nicht automatisch stoppen.

    Jeder folgende Server wird die Adresse erneut nutzen. (Um Adressen nur einmal zu nutzen, schalte \"Nutze beständige Adressen\" in den Einstellungen aus.)", @@ -148,7 +148,7 @@ "error_cannot_create_downloads_dir": "Konnte den Ordner für den Empfängermodus nicht erstellen: {}", "receive_mode_downloads_dir": "Dateien, die dir geschickt werden, findest du in diesem Ordner: {}", "receive_mode_warning": "Achtung: Im Empfängermodus können Leute Dateien auf deinen Computer laden. Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", - "gui_receive_mode_warning": "Im Empfängermodus können Leute Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", + "gui_receive_mode_warning": "Im Empfängermodus können Personen Dateien auf deinen Computer laden.

    Einige Dateien können die Kontrolle über deinen Computer übernehmen, wenn du sie öffnest. Öffne nur Dateien von Personen, denen du vertraust oder wenn du genau weißt, was du tust.", "receive_mode_received_file": "Empfangen: {}", "gui_mode_share_button": "Versende Dateien", "gui_mode_receive_button": "Empfange Dateien", @@ -170,16 +170,20 @@ "gui_settings_language_label": "Bevorzugte Sprache", "gui_settings_language_changed_notice": "Starte OnionShare neu, damit die Sprache geändert wird.", "help_config": "Ort deiner eigenen JSON Konfigurationsdatei (optional)", - "timeout_upload_still_running": "Warte bis der Upload abgeschlossen wurde", + "timeout_upload_still_running": "Warte bis Upload vollständig ist", "gui_settings_stealth_hidservauth_string": "Da dein privater Schlüssel jetzt gespeichert wurde um ihn später erneut zu nutzen, kannst du jetzt\nklicken um deinen HidServAuth zu kopieren.", - "gui_settings_connection_type_bundled_option": "Benutzt die in OnionShare eingebaute Tor-Version", + "gui_settings_connection_type_bundled_option": "Die integrierte Tor version von OnionShare nutzen", "settings_error_socket_file": "Kann nicht mittels des Tor Controller Socket {} verbinden.", - "gui_server_started_after_timeout": "Der Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", - "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das teilen zu starten.", + "gui_server_started_after_timeout": "Die Zeit ist abgelaufen bevor der Server gestartet werden konnte.\nBitte erneut etwas teilen.", + "gui_server_timeout_expired": "Der Timer ist bereits abgelaufen.\nBearbeite diesen um das Teilen zu starten.", "gui_status_indicator_share_stopped": "Bereit zum teilen", "history_in_progress_tooltip": "{} läuft", "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", "systray_page_loaded_title": "OnionShare Seite geladen", "gui_upload_finished_range": "{} hochgeladen zu {}", - "gui_upload_finished": "{} hochgeladen" + "gui_upload_finished": "{} hochgeladen", + "gui_add_files": "Dateien hinzufügen", + "gui_add_folder": "Ordner hinzufügen", + "gui_connect_to_tor_for_onion_settings": "Verbinde dich mit Tor, um die Einstellungen für onion services zu sehen", + "gui_url_label_onetime_and_persistent": "Dieser Server wird nicht automatisch stoppen. >br>
    Jeder nachfolgende Server wird die gleiche Adresse nutzen. (Um jedes mal eine andere Adresse zu nutzen, schalte \"Nutze eine gleichbleibende Adresse\" in den Einstellungen aus.)" } diff --git a/share/locale/es.json b/share/locale/es.json index e18c54a0..ce6c0fd2 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -36,7 +36,7 @@ "error_stealth_not_supported": "Para usar autorización de cliente, necesitas al menos Tor 0.2.9.1-alfa (o Navegador Tor 6.5) y python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", - "gui_settings_stealth_option": "Utilizar la autorización del cliente (antigua)", + "gui_settings_stealth_option": "Utilizar autorización de cliente", "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", @@ -80,7 +80,7 @@ "settings_error_bundled_tor_not_supported": "La versión de Tor que viene con OnionShare no funciona en el modo desarrollador en Windows o macOS.", "settings_error_bundled_tor_timeout": "La conexión a Tor está llevando demasiado tiempo. ¿Quizás el equipo está desconectado de Internet, o tu reloj no está en hora?", "settings_error_bundled_tor_broken": "OnionShare no pudo conectarse a Tor en segundo plano:\n{}", - "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", + "settings_test_success": "Conectado al controlador Tor.\n\nVersión de Tor: {}.\nSoporta servicios cebolla efímeros: {}.\nSoporta autenticación de cliente: {}.\nSoporta direcciones cebolla .onion de nueva generación: {}.", "error_tor_protocol_error": "Hubo un error con Tor: {}", "error_tor_protocol_error_unknown": "Hubo un error desconocido con Tor", "error_invalid_private_key": "Este tipo de clave privada no está soportado", @@ -95,11 +95,11 @@ "gui_tor_connection_error_settings": "Intenta cambiando la forma en que OnionShare se conecta a la red Tor en tu configuración.", "gui_tor_connection_canceled": "No se pudo conectar con Tor.\n\nAsegúrate de estar conectado a Internet, luego vuelve a abrir OnionShare y configurar tu conexión a Tor.", "gui_tor_connection_lost": "Desconectado de Tor.", - "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea nueva conexión compartida.", + "gui_server_started_after_timeout": "El temporizador de parada automática se agotó antes de que se iniciara el servidor.\nPor favor crea una nueva conexión compartida.", "gui_server_timeout_expired": "El temporizador de parada automática ya se ha agotado.\nPor favor, actualízalo para comenzar a compartir.", "share_via_onionshare": "Compártelo con OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar direcciones antiguas", - "gui_save_private_key_checkbox": "Usar una dirección persistente (modo antiguo)", + "gui_save_private_key_checkbox": "Usar una dirección persistente", "gui_share_url_description": "Cualquier persona con esta dirección de OnionShare puede descargar tus archivos usando el Navegador Tor: ", "gui_receive_url_description": "Con esta dirección de OnionShare, cualquier persona puede subir archivos a tu ordenador usando el Navegador Tor: ", "gui_url_label_persistent": "Este recurso compartido no se detendrá automáticamente.

    Cada recurso compartido subsiguiente reutilizará la dirección. (Para usar direcciones una sola vez, desactive la opción \"Usar dirección persistente\" en los ajustes.)", @@ -186,5 +186,8 @@ "gui_settings_language_label": "Idioma preferido", "gui_settings_language_changed_notice": "Reinicia OnionShare para que el cambio de idioma surta efecto.", "gui_upload_finished_range": "Cargado {} a {}", - "timeout_upload_still_running": "Esperando a que se complete la subida" + "timeout_upload_still_running": "Esperando a que se complete la subida", + "gui_add_files": "Añadir Archivos", + "gui_add_folder": "Añadir Carpeta", + "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla" } diff --git a/share/locale/fa.json b/share/locale/fa.json index d45ea27c..21ebbec5 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -1,73 +1,73 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", + "config_onion_service": "آماده سازی سرویس onion روی پورت {0:d}.", + "preparing_files": "فشرده سازی فایل ها.", + "give_this_url": "دادن این آدرس به گیرنده:", + "give_this_url_stealth": "دادن این آدرس و خط HidServAuth به گیرنده:", + "give_this_url_receive": "دادن این آدرس به ارسال کننده:", + "give_this_url_receive_stealth": "دادن این آدرس و HidServAuth به ارسال کننده:", + "ctrlc_to_stop": "برای توقف سرور Ctrl+C را فشار دهید", + "not_a_file": "{0:s} یک فایل معتبر نمی باشد.", + "not_a_readable_file": "{0:s} قابل خواندن نمی باشد.", + "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد.", + "other_page_loaded": "آدرس بارگذاری شد", "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "closing_automatically": "متوقف شد چون دانلود به پایان رسید", + "timeout_download_still_running": "انتظار برای تکمیل دانلود", + "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد", "systray_menu_exit": "خروج", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", + "systray_download_started_title": "دانلود OnionShare آغاز شد", + "systray_download_started_message": "یک کاربر شروع به دانلود فایل های شما کرد", + "systray_download_completed_title": "دانلود OnionShare به پایان رسید", + "systray_download_completed_message": "دانلود فایل های شما توسط کاربر به پایان رسید", + "systray_download_canceled_title": "دانلود OnionShare لغو شد", + "systray_download_canceled_message": "کاربر دانلود را لغو کرد", + "systray_upload_started_title": "آپلود OnionShare آغاز شد", + "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است", "help_local_only": "", - "help_stay_open": "", + "help_stay_open": "ادامه اشتراک گذاری پس از اولین دانلود", "help_shutdown_timeout": "", "help_stealth": "", "help_receive": "", "help_debug": "", - "help_filename": "", + "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری", "help_config": "", "gui_drag_and_drop": "", "gui_add": "افزودن", - "gui_delete": "Delete", - "gui_choose_items": "گزینش", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", + "gui_delete": "حذف", + "gui_choose_items": "انتخاب", + "gui_share_start_server": "شروع اشتراک گذاری", + "gui_share_stop_server": "توقف اشتراک گذاری", + "gui_share_stop_server_shutdown_timeout": "توقف اشتراک گذاری ({} ثانیه باقیمانده)", "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", + "gui_receive_start_server": "شروع حالت دریافت", + "gui_receive_stop_server": "توقف حالت دریافت", "gui_receive_stop_server_shutdown_timeout": "", "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", + "gui_copy_url": "کپی آدرس", + "gui_copy_hidservauth": "کپی HidServAuth", + "gui_downloads": "دانلود تاریخچه", "gui_no_downloads": "", "gui_canceled": "لغو شده", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_copied_url_title": "آدرس OnionShare کپی شد", + "gui_copied_url": "آدرس OnionShare بر کلیپ بورد کپی شد", + "gui_copied_hidservauth_title": "HidServAuth کپی شد", + "gui_copied_hidservauth": "خط HidServAuth بر کلیپ بورد کپی شد", + "gui_please_wait": "در حال آغاز... برای لغو کلیک کنید.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_quit_title": "نه به این سرعت", + "gui_share_quit_warning": "شما در پروسه ارسال فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", + "gui_receive_quit_warning": "شما در پروسه دریافت فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_quit_warning_quit": "خروج", "gui_quit_warning_dont_quit": "لغو", "error_rate_limit": "", - "zip_progress_bar_format": "", + "zip_progress_bar_format": "فشرده سازی: %p%", "error_stealth_not_supported": "", "error_ephemeral_not_supported": "", "gui_settings_window_title": "تنظیمات", - "gui_settings_whats_this": "", + "gui_settings_whats_this": "این چیست؟", "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", @@ -75,8 +75,8 @@ "gui_settings_autoupdate_timestamp": "", "gui_settings_autoupdate_timestamp_never": "هرگز", "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "تنظیمات عمومی", - "gui_settings_sharing_label": "", + "gui_settings_general_label": "تنظیمات کلی", + "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", "gui_settings_close_after_first_download_option": "", "gui_settings_connection_type_label": "", "gui_settings_connection_type_bundled_option": "", @@ -86,13 +86,13 @@ "gui_settings_connection_type_test_button": "", "gui_settings_control_port_label": "", "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", + "gui_settings_socks_label": "پورت SOCKS", "gui_settings_authenticate_label": "", "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "Password", - "gui_settings_password_label": "Password", + "gui_settings_authenticate_password_option": "رمز عبور", + "gui_settings_password_label": "رمز عبور", "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج", "gui_settings_tor_bridges_obfs4_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", @@ -178,8 +178,11 @@ "gui_upload_in_progress": "", "gui_upload_finished_range": "", "gui_upload_finished": "", - "gui_download_in_progress": "", + "gui_download_in_progress": "دانلود آغاز شد {}", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "زبان ترجیحی", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "انتظار برای تکمیل آپلود", + "gui_add_files": "افزودن فایل ها", + "gui_add_folder": "افزودن پوشه" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 38580eb1..275fd80a 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -18,7 +18,7 @@ "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", "gui_add": "Ajouter", "gui_delete": "Supprimer", - "gui_choose_items": "Sélectionnez", + "gui_choose_items": "Sélectionner", "gui_share_start_server": "Commencer à partager", "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier l'adresse", @@ -106,7 +106,7 @@ "gui_tor_connection_ask_quit": "Quitter", "gui_tor_connection_lost": "Déconnecté de Tor.", "share_via_onionshare": "Partager via OnionShare", - "gui_save_private_key_checkbox": "Utiliser une adresse persistante (legacy)", + "gui_save_private_key_checkbox": "Utiliser une adresse persistante", "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", @@ -124,8 +124,8 @@ "history_in_progress_tooltip": "{} en cours", "history_completed_tooltip": "{} terminé", "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", - "receive_mode_warning": "Avertissement : le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", - "gui_receive_mode_warning": "Le mode réception permet à des personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des choses provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_warning": "Avertissement : le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", "receive_mode_received_file": "Reçu : {}", "gui_mode_share_button": "Fichiers partagés", "gui_mode_receive_button": "Fichiers reçus", @@ -154,7 +154,7 @@ "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", - "gui_settings_stealth_option": "Utiliser l'autorisation client (legacy)", + "gui_settings_stealth_option": "Utiliser l'autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée, vous pouvez maintenant\ncliquer pour copier votre HidServAuth.", "gui_settings_autoupdate_check_button": "Vérifier les mises à jour", @@ -182,5 +182,7 @@ "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré" + "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré", + "gui_add_files": "Ajouter fichiers", + "gui_add_folder": "Ajouter dossier" } diff --git a/share/locale/he.json b/share/locale/he.json new file mode 100644 index 00000000..11c255f6 --- /dev/null +++ b/share/locale/he.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/it.json b/share/locale/it.json index 8d4f5bf3..17d4e770 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -1,87 +1,188 @@ { - "preparing_files": "Preparazione dei files da condividere.", - "give_this_url": "Dai questo URL alla persona a cui vuoi inviare il file:", - "ctrlc_to_stop": "Premi Ctrl-C per fermare il server", - "not_a_file": "{0:s} non è un file.", + "preparing_files": "Compressione dei file in corso.", + "give_this_url": "Dai questa URL al destinatario:", + "ctrlc_to_stop": "Premi Ctrl+C per fermare il server", + "not_a_file": "{0:s} non è un file valido.", "other_page_loaded": "URL caricato", - "closing_automatically": "Chiusura automatica dopo aver finito il download", - "large_filesize": "Attenzione: Inviare file di grandi dimensioni può richiedere ore", - "help_local_only": "Non usare tor: è solo per lo sviluppo", - "help_stay_open": "Mantieni il servizio nascosto avviato anche dopo aver finito il download", + "closing_automatically": "Chiusura automatica, download completato", + "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore", + "help_local_only": "Non usare Tor (solo per lo sviluppo)", + "help_stay_open": "Mantieni il servizio avviato anche dopo aver completato il primo download", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", - "gui_drag_and_drop": "Prendi e rilascia\ni file qui sopra", + "gui_drag_and_drop": "Trascina e rilascia i file e le cartelle per iniziare la condivisione", "gui_add": "Aggiungi", "gui_delete": "Cancella", "gui_choose_items": "Scegli", "gui_share_start_server": "Inizia la condivisione", - "gui_share_stop_server": "Ferma la condivisione", - "gui_copy_url": "Copia l' URL", - "gui_downloads": "Cronologia Dei Downloads", - "gui_canceled": "Cancellati", - "gui_copied_url": "URL Copiato nella clipboard", - "gui_please_wait": "Attendere prego...", - "zip_progress_bar_format": "Elaborazione files: %p%", - "config_onion_service": "Preparando il servizio onion sulla porta {0,d}.", - "give_this_url_stealth": "Dai questa URL e la linea di HidServAuth al recipiente:", + "gui_share_stop_server": "Arresta la condivisione", + "gui_copy_url": "Copia la URL", + "gui_downloads": "Cronologia dei Download", + "gui_canceled": "Annullato", + "gui_copied_url": "URL Copiato negli appunti", + "gui_please_wait": "Avviato... Cliccare per interrompere.", + "zip_progress_bar_format": "Compressione in corso: %p%", + "config_onion_service": "Preparando il servizio onion sulla porta {0:d}.", + "give_this_url_stealth": "Dai questa URL e la linea HidServAuth al destinatario:", "give_this_url_receive": "Dai questo indirizzo al mittente:", - "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth:", - "not_a_readable_file": "{0:s} non e' un file leggibile.", - "no_available_port": "Non trovo nessuna porta disponibile per far partire un onion service", - "close_on_timeout": "tempo scaduto, chiudo", + "give_this_url_receive_stealth": "Condividi questo indirizzo e la linea HideServAuth con il mittente:", + "not_a_readable_file": "{0:s} non è un file leggibile.", + "no_available_port": "Non è stato possibile trovare alcuna porta per avviare il servizio onion", + "close_on_timeout": "Arrestato per tempo scaduto", "timeout_download_still_running": "download in corso, attendere", - "systray_menu_exit": "Fine", - "systray_download_started_title": "Inizio il Download con OnionShare", - "systray_download_started_message": "Una persona ha iniziato il download dei tuoi file", + "systray_menu_exit": "Termina", + "systray_download_started_title": "Download con OnionShare avviato", + "systray_download_started_message": "Un utente ha iniziato il download dei tuoi file", "systray_download_completed_title": "Download completato", - "systray_download_completed_message": "il download dei tuoi file è stato completato", - "systray_download_canceled_title": "Download cancellato", - "systray_download_canceled_message": "la persona ha interrotto il download", - "systray_upload_started_title": "Iniziando upload", - "systray_upload_started_message": "Iniziando upload di file sul tuo computer", + "systray_download_completed_message": "L'utente ha terminato il download dei tuoi file", + "systray_download_canceled_title": "OnionShare Download cancellato", + "systray_download_canceled_message": "L'utente ha interrotto il download", + "systray_upload_started_title": "Upload con OnionShare avviato", + "systray_upload_started_message": "Un utente ha avviato l'upload di file sul tuo computer", "help_shutdown_timeout": "Termina la condivisione dopo alcuni secondi", - "help_stealth": "Richiedi autorizzazione (avanzato)", - "help_config": "Specifica il path del file JSON personalizzato", - "gui_share_stop_server_shutdown_timeout": "Ferma la condivisione ({}s rimanenti)", - "gui_share_stop_server_shutdown_timeout_tooltip": "timer termina in {}", - "gui_receive_start_server": "Inizia modalitá ricezione", - "gui_receive_stop_server": "Termina modalitá ricezione", - "gui_receive_stop_server_shutdown_timeout": "Interrompi modalitá ricezione ({}s rimanenti)", - "gui_receive_stop_server_shutdown_timeout_tooltip": "timer termina in {}", - "gui_copy_hidservauth": "Copia URL segreto", - "gui_no_downloads": "ancora niente", + "help_stealth": "Usa l'autorizzazione del client (avanzato)", + "help_config": "Specifica il percorso del file di configurazione del JSON personalizzato", + "gui_share_stop_server_shutdown_timeout": "Arresta la condivisione ({}s rimanenti)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Il timer si arresterà tra {}", + "gui_receive_start_server": "Inizia la ricezione", + "gui_receive_stop_server": "Arresta la ricezione", + "gui_receive_stop_server_shutdown_timeout": "Interrompi la ricezione ({}s rimanenti)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Il timer termina tra {}", + "gui_copy_hidservauth": "Copia HidServAuth", + "gui_no_downloads": "Ancora nessun Download", "gui_copied_url_title": "Indirizzo OnionShare copiato", - "gui_copied_hidservauth_title": "URL segreto copiato", - "gui_copied_hidservauth": "URL segreto copiato", - "gui_download_upload_progress_complete": "%p%, {0:s} mancanti.", - "gui_download_upload_progress_starting": "{0:s}, %p% (calcolando)", + "gui_copied_hidservauth_title": "HidServAuth copiato", + "gui_copied_hidservauth": "HidServAuth copiato negli appunti", + "gui_download_upload_progress_complete": "%p%, {0:s} trascorsi.", + "gui_download_upload_progress_starting": "{0:s}, %p% (calcolato)", "gui_download_upload_progress_eta": "{0:s}, Terminando in: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org", - "gui_quit_title": "Non cosí in fretta", - "gui_share_quit_warning": "Stai per inviare dai file. Sei sicuro di voler uscire da OnionShare?", - "gui_receive_quit_warning": "Stai ricevendo file, vuoi davvero terminare e chiudere OnionShare?", + "gui_quit_title": "Non così in fretta", + "gui_share_quit_warning": "Stai per inviare dei file. Sei sicuro di voler uscire da OnionShare?", + "gui_receive_quit_warning": "Stai per ricevere dei file, vuoi davvero terminare e chiudere OnionShare?", "gui_quit_warning_quit": "Esci", "gui_quit_warning_dont_quit": "Cancella", - "error_rate_limit": "Qualcosa ha fatto troppi tentativi a questo indirizzo, questo potrebbe esporre la sicurezza dell'indirizzo, quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL che verrá generato.", - "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", + "error_rate_limit": "Qualcuno ha tentato troppe volte di accedere al tuo indirizzo, questo potrebbe comprometterne la sicurezza quindi OnionShare ha deciso di interrompere il server. Prova a condividere di nuovo e invia al tuo contatto il nuovo URL.", + "error_stealth_not_supported": "Per usare l'opzione \"client auth\" hai bisogno almeno della versione di Tor 0.2.9.1-alpha (o Tor Browser 6.5) con python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare richiede almeno Tor 0.2.7.1 e python3-stem 1.4.0.", - "gui_settings_window_title": "Preferenze", - "gui_settings_whats_this": "Cos'e' questo?", - "help_receive": "Ricevi condivisioni invece di inviarle", + "gui_settings_window_title": "Impostazioni", + "gui_settings_whats_this": "Cos'è questo?", + "help_receive": "Ricevi le condivisioni invece di inviarle", "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", - "gui_settings_stealth_hidservauth_string": "Dopo aver salvato la tua chiave privata per il riutilizzo, significa che ora puoi\nclicca per copiare il tuo HidServAuth.", - "gui_settings_autoupdate_label": "controlla la nuova versione", - "gui_settings_autoupdate_option": "Notificami quando una nuova versione è disponibile", - "gui_settings_autoupdate_timestamp": "ultimo controllo", + "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi\ncliccare per copiare il tuo HidServAuth.", + "gui_settings_autoupdate_label": "Controlla se vi sono nuove versioni", + "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione", + "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}", "gui_settings_autoupdate_timestamp_never": "Mai", - "gui_settings_autoupdate_check_button": "Controlla per una nuova nuova versione", + "gui_settings_autoupdate_check_button": "Controlla per una nuova versione", "gui_settings_general_label": "Impostazioni generali", - "gui_settings_sharing_label": "Impostazione Condivise", - "gui_settings_close_after_first_download_option": "Ferma la condivisione dopo il primo download", - "gui_settings_connection_type_label": "Come OnionShare si connette a tor?", + "gui_settings_sharing_label": "Sto condividendo le impostazioni", + "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo il primo download", + "gui_settings_connection_type_label": "Come si dovrebbe connettere OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", "gui_settings_language_label": "Lingua preferita", "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", - "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati" + "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", + "timeout_upload_still_running": "In attesa del completamento dell'upload", + "gui_add_files": "Aggiungi file", + "gui_add_folder": "Aggiungi una cartella", + "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo", + "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket", + "gui_settings_connection_type_test_button": "Prova la connessione Tor", + "gui_settings_socket_file_label": "File di socket", + "gui_settings_socks_label": "Porta SOCKS", + "gui_settings_authenticate_label": "Impostazioni di autenticazione Tor", + "gui_settings_authenticate_password_option": "Password", + "gui_settings_password_label": "Password", + "gui_settings_control_port_label": "Porta di controllo", + "gui_settings_authenticate_no_auth_option": "Nessuna autenticazione o autenticazione tramite cookie", + "gui_settings_tor_bridges": "Supporto bridge Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Non usare i bridge", + "gui_settings_tor_bridges_obfs4_radio_option": "Usare i trasporti obfs4 integrati selezionabili", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Usare i trasporti obfs4 integrati selezionabili (richiede obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Usare i trasporti integrati meek_lite (Azure) selezionabili", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Usare i trasporti integrati meek_lite (Azure) selezionabili (richiede obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Attenzione: i bridge meek_lite sono molto pesanti per l'esecuzione del progetto Tor.

    Da usare solo se impossibile connettersi a Tor direttamente, con obfs4, o altri bridge normali.", + "gui_settings_tor_bridges_custom_label": "Puoi prendere i bridge da 1 https://bridges.torproject.org2", + "gui_settings_tor_bridges_invalid": "Nessuno dei bridge che hai aggiunto funzionano\nControllali nuovamente o aggiungine altri.", + "gui_settings_button_save": "Salva", + "gui_settings_button_cancel": "Cancella", + "gui_settings_button_help": "Aiuto", + "gui_settings_shutdown_timeout_checkbox": "Utilizza il timer di arresto automatico", + "gui_settings_shutdown_timeout": "Ferma la condivisione alle:", + "settings_error_unknown": "Impossibile connettersi al controller Tor perché le tue impostazioni non hanno senso.", + "settings_error_automatic": "Impossibile connettersi al controller Tor. Tor Browser (disponibile da torproject.org) è in esecuzione in background?", + "settings_error_socket_port": "Impossibile connettersi al controller Tor in {}: {}.", + "settings_error_socket_file": "Impossibile connettersi al controller Tor utilizzando il file socket {}.", + "settings_error_auth": "Connesso a {}: {}, ma non può autenticarsi. Forse questo non è un controller Tor?", + "settings_error_missing_password": "Collegato al controller Tor, ma richiede una password per l'autenticazione.", + "settings_error_unreadable_cookie_file": "Collegato al controller Tor, ma la password potrebbe essere errata o l'utente non è autorizzato a leggere il file cookie.", + "settings_error_bundled_tor_not_supported": "L'uso della versione Tor fornita con OnionShare non funziona in modalità sviluppatore su Windows o macOS.", + "settings_error_bundled_tor_timeout": "Prendendo troppo tempo per connettersi a Tor. Forse non sei connesso a Internet o hai un orologio di sistema impreciso?", + "settings_error_bundled_tor_broken": "OnionShare non è riuscito a connettersi a Tor in background:\n{}", + "settings_test_success": "Collegato al controller Tor.\n\nVersione Tor: {}\nSupporta servizi onion effimeri: {}.\nSupporta l'autenticazione del client: {}.\nSupporta indirizzi .onion next-gen: {}.", + "error_tor_protocol_error": "Si è verificato un errore con Tor: {}", + "error_tor_protocol_error_unknown": "Si è verificato un errore sconosciuto con Tor", + "error_invalid_private_key": "Questo tipo di chiave privata non è supportato", + "connecting_to_tor": "In connessione alla rete Tor", + "update_available": "E' disponibile una nuova versione di OnionShare. Clicca qui per scaricarla.

    Stai usando {} e l'ultima versione è {}.", + "update_error_check_error": "Non è possibile verificare per nuove versioni: il sito OnionShare dice che l'ultima versione non è riconoscibile '{}'…", + "update_error_invalid_latest_version": "Non è possibile controllare per una nuova versione: Magari non sei connesso a Tor, o il sito OnionShare non funziona?", + "update_not_available": "Stai usando la ultima versione di OnionShare.", + "gui_tor_connection_ask": "Apri le impostazione per trovare la connessione a Tor?", + "gui_tor_connection_ask_open_settings": "Sì", + "gui_tor_connection_ask_quit": "Esci", + "gui_tor_connection_error_settings": "Prova a modificare le impostazioni di come OnionShare si connette alla rete Tor.", + "gui_tor_connection_canceled": "Impossibile connettersi a Tor,\n\nVerifica la connessione a Internet, dopo prova a riaprire OnionShare e configurare la connessione a Tor.", + "gui_tor_connection_lost": "Disconnesso da Tor.", + "gui_server_started_after_timeout": "Il timer auto-stop si è esaurito prima dell'avvio del server.\nSi prega di fare una nuova condivisione.", + "gui_server_timeout_expired": "Il timer auto-stop ha già finito.\nPer favore aggiornalo per iniziare la condivisione.", + "share_via_onionshare": "Usa OnionShare", + "gui_connect_to_tor_for_onion_settings": "Connetti a Tor per vedere le impostazioni del servizio onion", + "gui_use_legacy_v2_onions_checkbox": "Usa gli indirizzi legacy", + "gui_save_private_key_checkbox": "Usa un indirizzo persistente", + "gui_share_url_description": "1 Tutti2 con questo l'indirizzo di OnionShare possono 3 scaricare4 i tuoi file usando 5 il Browser Tor6: 7", + "gui_receive_url_description": "1 Tutti2 con questo indirizzo OnionShare possono 3 caricare4 file nel tuo computer usando 5 Tor Browser6: 7", + "gui_url_label_persistent": "Questa condivisione non si arresterà automaticamente.

    Ogni successiva condivisione riutilizza l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)", + "gui_url_label_stay_open": "Questa condivisione non si arresterà automaticamente.", + "gui_url_label_onetime": "Questa condivisione verrà interrotta dopo il primo completamento.", + "gui_url_label_onetime_and_persistent": "Questa condivisione non si arresterà automaticamente.

    Ogni condivisione successiva riutilizzerà l'indirizzo. (Per utilizzare indirizzi monouso, disattivare \"Usa indirizzo persistente\" nelle impostazioni.)", + "gui_status_indicator_share_stopped": "Pronto per condividere", + "gui_status_indicator_share_working": "Iniziando…", + "gui_status_indicator_share_started": "Condividendo", + "gui_status_indicator_receive_stopped": "Pronto per ricevere", + "gui_status_indicator_receive_working": "Iniziando…", + "gui_status_indicator_receive_started": "Ricevendo", + "gui_file_info": "{} file, {}", + "gui_file_info_single": "{} file, {}", + "history_in_progress_tooltip": "{} in avanzamento", + "history_completed_tooltip": "{} completato", + "info_in_progress_uploads_tooltip": "{} upload(s) in avanzamento", + "info_completed_uploads_tooltip": "{} upload(s) completati", + "error_cannot_create_downloads_dir": "Non è stato possibile creare la cartella in modalità ricezione: {}", + "receive_mode_downloads_dir": "I file a te mandati appariranno in questa cartella: {}", + "receive_mode_warning": "Attenzione: La modalità ricezione permette alla gente di fare l'upload di file nel tuo computer. Alcuni file possono potenzialmente prendere il controllo del tuo computer se aperti. Apri solamente file inviati da persone di cui ti fidi, o se sai quello che stai facendo.", + "gui_receive_mode_warning": "La modalità ricezione permette alle persone di fare l'upload di file nel tuo computer.

    Alcuni file possono potenzialmente prendere il controllo del tuo computer se li apri. Apri solamente file di persone di cui ti fidi, o se sai quello che stai facendo.", + "receive_mode_upload_starting": "Upload di dimensione totale {} sta partendo", + "receive_mode_received_file": "Ricevuto: {}", + "gui_mode_share_button": "Condividi File", + "gui_mode_receive_button": "Ricevi File", + "gui_settings_receiving_label": "Impostazioni di Ricezione", + "gui_settings_downloads_label": "Salva i file in", + "gui_settings_downloads_button": "Navigare", + "gui_settings_public_mode_checkbox": "Modalità pubblica", + "systray_close_server_title": "Il server OnionShare è inattivo", + "systray_close_server_message": "Un utente ha disattivato il Server", + "systray_page_loaded_title": "La pagina di OnionShare è stata caricata", + "systray_download_page_loaded_message": "Un utente ha caricato la pagina di Download", + "systray_upload_page_loaded_message": "Un utente ha caricato la pagina di Upload", + "gui_uploads": "Storia degli Upload", + "gui_no_uploads": "Nessun Upload ancora", + "gui_clear_history": "Pulisci tutto", + "gui_upload_in_progress": "Upload iniziato {}", + "gui_upload_finished_range": "Upload eseguito {} a {}", + "gui_upload_finished": "Caricato {}", + "gui_download_in_progress": "Download iniziato {}", + "gui_open_folder_error_nautilus": "Impossibile aprire la cartella perché Nautilus non è disponibile. Il file è qui: {}" } diff --git a/share/locale/ja.json b/share/locale/ja.json new file mode 100644 index 00000000..acddc8c2 --- /dev/null +++ b/share/locale/ja.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "{0:d}番ポートを使ってonionサービス設定中...", + "preparing_files": "ファイル圧縮中...", + "give_this_url": "このアドレスを受領者と共有して下さい。", + "give_this_url_stealth": "このアドレスとHidServAuth行を受領者と共有して下さい。", + "give_this_url_receive": "このアドレスを送信者と共有して下さい。", + "give_this_url_receive_stealth": "このアドレスとHidServAuth行を送信者と共有して下さい。", + "ctrlc_to_stop": "Ctrl+Cキーでサーバーをシャットダウンする", + "not_a_file": "{0:s}は有効なファイルではありません。", + "not_a_readable_file": "{0:s}は読めるファイルではありません。", + "no_available_port": "onionサービスを実行するための利用可能ポートを見つかりません", + "other_page_loaded": "アドレスはロードされています", + "close_on_timeout": "自動タイマーがタイムアウトしたため停止されました", + "closing_automatically": "ダウンロードが完了されたため停止されました", + "timeout_download_still_running": "ダウンロード完了待ち", + "timeout_upload_still_running": "アップロード完了待ち", + "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない", + "systray_menu_exit": "終了", + "systray_download_started_title": "OnionShareダウンロードは開始されました", + "systray_download_started_message": "ユーザーがダウンロードを開始しました", + "systray_download_completed_title": "OnionShareダウンロード完了", + "systray_download_completed_message": "ユーザーがダウンロードし終えました", + "systray_download_canceled_title": "OnionShareダウンロードは中止されました", + "systray_download_canceled_message": "ユーザーがダウンロードを中止しました", + "systray_upload_started_title": "OnionShareアップロードは開始されました", + "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました", + "help_local_only": "Torを使わない(開発利用のみ)", + "help_stay_open": "最初ダウンロード後に共有し続けます", + "help_shutdown_timeout": "数秒後に共有が停止されます", + "help_stealth": "クライアント認証を使う(上級者向け)", + "help_receive": "送信の代わりに受信を優先する", + "help_debug": "OnionShareのエラーを標準出力に、Webのエラーをディスクに記録する", + "help_filename": "共有するファイルとフォルダの一覧", + "help_config": "カスタムJSON設定ファイルの位置(任意)", + "gui_drag_and_drop": "共有を始めるにはファイルやフォルダをドラッグアンドドロップしてください", + "gui_add": "追加", + "gui_add_files": "ファイルを追加", + "gui_add_folder": "フォルダを追加", + "gui_delete": "削除", + "gui_choose_items": "選択", + "gui_share_start_server": "共有を開始する", + "gui_share_stop_server": "共有を停止する", + "gui_share_stop_server_shutdown_timeout": "共有を停止中です(残り{}秒)", + "gui_share_stop_server_shutdown_timeout_tooltip": "{}に自動停止します", + "gui_receive_start_server": "受信モードを開始", + "gui_receive_stop_server": "受信モードを停止", + "gui_receive_stop_server_shutdown_timeout": "受信モードを停止中(残り{}秒)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "{}に自動停止します", + "gui_copy_url": "アドレスをコピー", + "gui_copy_hidservauth": "HidServAuthをコピー", + "gui_downloads": "ダウンロード履歴", + "gui_no_downloads": "まだダウンロードがありません", + "gui_canceled": "キャンセルされました", + "gui_copied_url_title": "OnionShareのアドレスをコピーしました", + "gui_copied_url": "OnionShareのアドレスをクリップボードへコピーしました", + "gui_copied_hidservauth_title": "HidServAuthをコピーしました", + "gui_copied_hidservauth": "HidServAuthの行をクリップボードへコピーしました", + "gui_please_wait": "実行中… クリックでキャンセルします。", + "gui_download_upload_progress_complete": "%p%、 経過時間 ({0:s})。", + "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)", + "gui_download_upload_progress_eta": "{0:s} 終了予定:{1:s}、%p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "ちょっと待って", + "gui_share_quit_warning": "ファイルを送信中です。本当にOnionShareを終了しますか?", + "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?", + "gui_quit_warning_quit": "終了", + "gui_quit_warning_dont_quit": "キャンセル", + "error_rate_limit": "誰かが何度も間違えたアドレスをアクセスして試みるので、不正アクセスしようとする可能性があります。セキュリティーのためにOnionShareはサーバーを停止しました。再び共有し始めて、受領者に新しいアドレスに送って下さい。", + "zip_progress_bar_format": "圧縮中: %p%", + "error_stealth_not_supported": "クライアント認証を使用するのに、少なくともTor 0.2.9.1-alpha (それともTor Browser 6.5)とpython3-stem 1.5.0が必要です。", + "error_ephemeral_not_supported": "OnionShareは少なくともTor 0.2.7.1とpython3-stem 1.4.0が必要です。", + "gui_settings_window_title": "設定", + "gui_settings_whats_this": "これは何ですか?", + "gui_settings_stealth_option": "クライアント認証を使用", + "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。", + "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする", + "gui_settings_autoupdate_option": "更新通知を起動します", + "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}", + "gui_settings_autoupdate_timestamp_never": "したことがない", + "gui_settings_autoupdate_check_button": "更新をチェックする", + "gui_settings_general_label": "一般的設定", + "gui_settings_sharing_label": "共有設定", + "gui_settings_close_after_first_download_option": "最初のダウンロード後に停止する", + "gui_settings_connection_type_label": "OnionShareがどうやってTorと接続して欲しい?", + "gui_settings_connection_type_bundled_option": "OnionShareに組み込まれるTorバージョンを使用する", + "gui_settings_connection_type_automatic_option": "Torブラウザと自動設定してみる", + "gui_settings_connection_type_control_port_option": "コントロールポートを使用して接続する", + "gui_settings_connection_type_socket_file_option": "ソケットファイルを使用して接続する", + "gui_settings_connection_type_test_button": "Torへの接続をテストする", + "gui_settings_control_port_label": "コントロールポート", + "gui_settings_socket_file_label": "ソケットファイル", + "gui_settings_socks_label": "SOCKSポート", + "gui_settings_authenticate_label": "Tor認証の設定", + "gui_settings_authenticate_no_auth_option": "認証なし、それともクッキー認証", + "gui_settings_authenticate_password_option": "パスワード", + "gui_settings_password_label": "パスワード", + "gui_settings_tor_bridges": "Torブリッジサポート", + "gui_settings_tor_bridges_no_bridges_radio_option": "ブリッジを使用しない", + "gui_settings_tor_bridges_obfs4_radio_option": "組み込みのobs4 pluggable transportを使用する", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "組み込みのobs4 pluggable transportを使用する(obsf4proxy必要)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "組み込みのmeek_lite (Azure) pluggable transportを使用する", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "組み込みのmeek_lite (Azure) pluggable transportを使用する(obsf4proxy必要)", + "gui_settings_meek_lite_expensive_warning": "警告:meek_liteブリッジはTor Projectにとって維持費がかさむ

    直接にTorと接続できない場合、あるいはobsf4ブリッジや他のブリッジが使用できない場合のみに使って下さい。", + "gui_settings_tor_bridges_custom_radio_option": "カスタムブリッジを使用する", + "gui_settings_tor_bridges_custom_label": "https://bridges.torproject.orgからブリッジを入手できます", + "gui_settings_tor_bridges_invalid": "全ての追加したブリッジは機能しませんでした。\n再確認して、あるいは他のを追加して下さい。", + "gui_settings_button_save": "保存", + "gui_settings_button_cancel": "キャンセル", + "gui_settings_button_help": "ヘルプ", + "gui_settings_shutdown_timeout_checkbox": "自動停止タイマーを使用する", + "gui_settings_shutdown_timeout": "共有を停止する時間:", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/ko.json b/share/locale/ko.json index c152c33a..d4ce9d38 100644 --- a/share/locale/ko.json +++ b/share/locale/ko.json @@ -1,34 +1,34 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "timeout_upload_still_running": "", - "large_filesize": "", + "config_onion_service": "어니언 서비스를 포트{0:d} 에서 설정하기.", + "preparing_files": "파일들을 압축하기.", + "give_this_url": "이 주소를 수신자에게 보내시오:", + "give_this_url_stealth": "이 주소와 그리고 HidServAuth 선을 수신자에게 보내시오:", + "give_this_url_receive": "이 주소를 발신자에게 보내시오:", + "give_this_url_receive_stealth": "이 주소와 그리고 HidServAuth를 발신자에 보내시오:", + "ctrlc_to_stop": "서버를 멈추기 위해 Ctrl+C 키를 누르시오", + "not_a_file": "{0:s} 는 유효하지 않은 파일입니다.", + "not_a_readable_file": "{0:s} 는 읽을수 없는 파일입니다.", + "no_available_port": "어니언 서비스를 시작하기 위한 사용 가능한 포트를 찾을수 없었습니다", + "other_page_loaded": "주소가 로드되다", + "close_on_timeout": "자동멈춤 타이머가 끝났기 때문에 정지되다", + "closing_automatically": "다운로드가 완료되었기 때문에 정지되다", + "timeout_download_still_running": "다운로드가 완료되기를 기다리는 중입니다", + "timeout_upload_still_running": "업로드가 완료되기를 기다리는 중입니다", + "large_filesize": "경고: 대용량의 자료를 보내는것은 오래 걸릴수 있습니다", "systray_menu_exit": "종료", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", + "systray_download_started_title": "어니언쉐어 다운로드가 시작됨", + "systray_download_started_message": "사용자가 당신의 파일들을 다운로딩 하기 시작했습니다", + "systray_download_completed_title": "어니언쉐어 다운로드가 완료됨", + "systray_download_completed_message": "사용자가 당신의 파일들을 다운로딩 하는것을 완료했습니다", + "systray_download_canceled_title": "어니언쉐어 다운로드가 취소됨", + "systray_download_canceled_message": "사용자가 다운로드를 취소했습니다", + "systray_upload_started_title": "어니언쉐어 업로드가 시작됨", + "systray_upload_started_message": "사용자가 파일들을 당신의 컴퓨터로 업로딩 하는것을 시작했습니다", + "help_local_only": "Tor를 사용하지 마시오 (오직 개발자용)", + "help_stay_open": "첫 다운로드 후 계속 공유하시오", + "help_shutdown_timeout": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오", + "help_stealth": "고객 허가를 사용 (고급 수준의)", + "help_receive": "그것들을 보내는것 대신 공유를 받으시오", "help_debug": "", "help_filename": "", "help_config": "", diff --git a/share/locale/nl.json b/share/locale/nl.json index 3ecc763b..39c5ba9f 100644 --- a/share/locale/nl.json +++ b/share/locale/nl.json @@ -23,10 +23,10 @@ "help_stay_open": "Blijven delen na afronden van eerste download", "help_shutdown_timeout": "Stoppen met delen na het opgegeven aantal seconden", "help_stealth": "Client-authorisatie gebruiken (geavanceerd)", - "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", + "help_debug": "Log OnionShare fouten naar stdout, en web fouten naar disk", "help_filename": "Lijst van bestanden of mappen om te delen", "help_config": "Instelbaar pad naar JSON configuratie bestand (optioneel)", - "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer", + "gui_drag_and_drop": "Sleep en zet\nbestanden hier neer om het delen te starten", "gui_add": "Toevoegen", "gui_delete": "Verwijder", "gui_choose_items": "Kies", @@ -41,22 +41,22 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (berekenen)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_share_quit_warning": "Weet je zeker dat je wilt afsluiten?\nDe URL die je aan het delen bent zal niet meer bestaan.", + "gui_share_quit_warning": "Je bent in het proces van bestanden versturen. Weet je zeker dat je OnionShare af wilt sluiten?", "gui_quit_warning_quit": "Afsluiten", - "gui_quit_warning_dont_quit": "Niet afsluiten", - "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", + "gui_quit_warning_dont_quit": "Annuleren", + "error_rate_limit": "Iemand heeft te veel foute pogingen gedaan op je adres, dit betekent dat ze zouden kunnen proberen het te raden, daarom heeft OnionShare de server gestopt. Start het delen opnieuw en stuur de ontvanger een nieuw adres om te delen.", "zip_progress_bar_format": "Comprimeren: %p%", "error_stealth_not_supported": "Om client authorization te gebruiken heb je op zijn minst zowel Tor 0.2.9.1-alpha (of Tor Browser 6.5) en python3-stem 1.5.0 nodig.", "error_ephemeral_not_supported": "OnionShare vereist minstens zowel Tor 0.2.7.1 als python3-stem 1.4.0.", "gui_settings_window_title": "Instellingen", - "gui_settings_stealth_option": "Gebruik client authorization (achterhaald)", - "gui_settings_autoupdate_label": "Controleer op updates", + "gui_settings_stealth_option": "Gebruik cliëntautorisatie", + "gui_settings_autoupdate_label": "Controleer op nieuwe versies", "gui_settings_autoupdate_option": "Laat me weten als er een nieuwe versie beschikbaar is", "gui_settings_autoupdate_timestamp": "Laatste controle: {}", "gui_settings_autoupdate_timestamp_never": "Nooit", "gui_settings_autoupdate_check_button": "Controleer op een Nieuwe Versie", "gui_settings_sharing_label": "Instelling voor delen", - "gui_settings_close_after_first_download_option": "Stop delen na eerste download", + "gui_settings_close_after_first_download_option": "Stop met delen na eerste download", "gui_settings_connection_type_label": "Hoe moet OnionShare verbinden met Tor?", "gui_settings_connection_type_bundled_option": "Gebruik de Tor versie die is ingebouwd in OnionShare", "gui_settings_connection_type_automatic_option": "Probeer auto-configuratie met Tor Browser", @@ -67,13 +67,13 @@ "gui_settings_socket_file_label": "Socket bestand", "gui_settings_socks_label": "SOCKS poort", "gui_settings_authenticate_label": "Tor authenticatie instellingen", - "gui_settings_authenticate_no_auth_option": "Geen authenticatie of cookie authenticatie", + "gui_settings_authenticate_no_auth_option": "Geen authenticatie, of cookie authenticatie", "gui_settings_authenticate_password_option": "Wachtwoord", "gui_settings_password_label": "Wachtwoord", "gui_settings_button_save": "Opslaan", "gui_settings_button_cancel": "Annuleren", "gui_settings_button_help": "Help", - "gui_settings_shutdown_timeout": "Stop delen om:", + "gui_settings_shutdown_timeout": "Stop het delen om:", "settings_saved": "Instellingen opgeslagen in {}", "settings_error_unknown": "Kan geen verbinding maken met de Tor controller omdat je instellingen nergens op slaan.", "settings_error_automatic": "Kon geen verbinding maken met de Tor controller. Draait Tor Browser (beschikbaar via torproject.org) in de achtergrond?", @@ -82,15 +82,15 @@ "settings_error_auth": "Verbonden met {}:{}, maar kan niet authenticeren. Misschien is het geen Tor controller?", "settings_error_missing_password": "Verbonden met Tor controller, maar het heeft een wachtwoord nodig voor authenticatie.", "settings_error_unreadable_cookie_file": "Verbonden met de Tor controller, maar het wachtwoord kan onjuist zijn, of je gebruiker heeft geen toestemming om het cookie bestand te lezen.", - "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de developer mode op Windows of macOS.", + "settings_error_bundled_tor_not_supported": "De Tor versie die is meegeleverd bij OnionShare werkt niet in de ontwikkelaarsmodus op Windows of macOS.", "settings_error_bundled_tor_timeout": "Verbinden met Tor duurt te lang. Misschien is je computer niet verbonden met internet, of je hebt een inaccurate systeemklok?", "settings_error_bundled_tor_broken": "OnionShare kan niet verbinden met Tor op de achtergrond:\n{}", "settings_test_success": "Verbonden met de Tor controller.\n\nTor versie: {}\nOndersteund ephemeral onion services: {}.\nOndersteund client authentication: {}.\nOndersteund next-gen .onion addresses: {}.", "error_tor_protocol_error": "Er was een fout met Tor: {}", "connecting_to_tor": "Verbinden met het Tor network", - "update_available": "Er is een nieuwe versie van OnionShare beschikbaar. Klik hier om te updaten.

    Je hebt nu {} en de laatste versie is {}.", + "update_available": "Nieuwe OnionShare is uitgekomen. Klik hier om hem te krijgen.

    Jij gebruikt {} and de laatste is {}.", "update_error_check_error": "Kon niet controleren op nieuwe versies: De OnionShare website meldt dat de laatste versie de onherkenbare is '{}' is…", - "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of is de OnionShare website is niet beschikbaar?", + "update_error_invalid_latest_version": "Kon niet controleren op een nieuwe versie: Wellicht ben je niet met Tor verbonden, of de OnionShare website is niet beschikbaar?", "update_not_available": "Je draait de laatst beschikbare OnionShare.", "gui_tor_connection_ask": "Open de instellingen om het verbindingsprobleem met Tor op te lossen?", "gui_tor_connection_ask_open_settings": "Ja", @@ -106,21 +106,21 @@ "systray_upload_started_message": "Een gebruiker is begonnen met uploaden van bestanden naar je computer", "help_receive": "Bestanden ontvangen in plaats van ze versturen", "timeout_upload_still_running": "Wachten op voltooiing van de upload", - "gui_share_start_server": "Start delen", - "gui_share_stop_server": "Stop delen", - "gui_share_stop_server_shutdown_timeout": "Stop Delen ({}s resterend)", + "gui_share_start_server": "Start met delen", + "gui_share_stop_server": "Stop met delen", + "gui_share_stop_server_shutdown_timeout": "Stop met Delen ({}s resterend)", "gui_share_stop_server_shutdown_timeout_tooltip": "Auto-stop timer eindigt bij {}", - "gui_receive_start_server": "Start Ontvangst Mode", - "gui_receive_stop_server": "Stop Ontvangst Mode", - "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangst Mode ({}s resterend)", + "gui_receive_start_server": "Start Ontvangstmodus", + "gui_receive_stop_server": "Stop Ontvangstmodus", + "gui_receive_stop_server_shutdown_timeout": "Stop Ontvangstmodus ({}s resterend)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer stopt bij {}", "gui_no_downloads": "Nog Geen Downloads", - "gui_copied_url_title": "Gekopieerd OnionShare Adres", + "gui_copied_url_title": "Gekopieerd OnionShare Adres", "gui_copied_hidservauth_title": "HidServAuth gekopieerd", "gui_quit_title": "Niet zo snel", - "gui_receive_quit_warning": "Je ben bezig files te ontvangen. Weet je zeker dat je OnionShare wilt stoppen?", - "gui_settings_whats_this": "1What is dit?2", - "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je klikken om je HidServAuth te kopiëren.", + "gui_receive_quit_warning": "Je bent in het proces van bestanden ontvangen. Weet je zeker dat je OnionShare af wilt sluiten?", + "gui_settings_whats_this": "1Wat is dit?2", + "gui_settings_stealth_hidservauth_string": "Nu de privésleutel voor hergebruik is opgeslagen kun je \nklikken om je HidServAuth te kopiëren.", "gui_settings_general_label": "Algemene instellingen", "gui_settings_tor_bridges": "Tor bridge ondersteuning", "gui_settings_tor_bridges_no_bridges_radio_option": "Gebruik geen bridges", @@ -131,13 +131,13 @@ "gui_settings_meek_lite_expensive_warning": "Waarschuwing: De meek_lite bridges zijn erg kostbaar voor het Tor Project om uit te voeren.

    Gebruik ze alleen als je niet direct met Tor kan verbinden, via obfs4 transports, of andere normale bridges.", "gui_settings_tor_bridges_custom_radio_option": "Gebruik custom bridges", "gui_settings_tor_bridges_custom_label": "Je kan bridges krijgen via 1https://bridges.torproject.org2", - "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. Controleer ze of voeg andere toe.", + "gui_settings_tor_bridges_invalid": "Geen van de bridges die je hebt toegevoegd werken. \nControleer ze of voeg andere toe.", "gui_settings_shutdown_timeout_checkbox": "Gebruik auto-stop timer", "error_tor_protocol_error_unknown": "Er was een onbekende fout met Tor", "error_invalid_private_key": "Dit type privésleutel wordt niet ondersteund", "gui_tor_connection_lost": "De verbinding met Tor is verbroken.", "gui_use_legacy_v2_onions_checkbox": "Gebruik ouderwetse adressen", - "gui_save_private_key_checkbox": "Gebruik een blijvend adres (achterhaald)", + "gui_save_private_key_checkbox": "Gebruik een blijvend adres", "gui_share_url_description": "1Iedereen2 met dit OnionShare adres kan je bestanden 3binnenhalen4 met de 5Tor Browser6: ", "gui_receive_url_description": "1Iedereen2 met dit OnionShare adres kan bestanden op je computer 3plaatsen4 met de 5Tor Browser6: 7", "gui_url_label_persistent": "Deze share stopt niet vanzelf.

    Elke volgende share hergebruikt het adres. (Om eenmalige adressen te gebruiken, zet \"Gebruik vast adres\" uit in de settings.)", @@ -156,7 +156,7 @@ "history_completed_tooltip": "{} klaar", "info_in_progress_uploads_tooltip": "{} upload(s) zijn bezig", "info_completed_uploads_tooltip": "de {} upload(s) zijn klaar", - "error_cannot_create_downloads_dir": "Kon de ontvangst mode map niet maken: {}", + "error_cannot_create_downloads_dir": "Kon de ontvangst modus map niet aanmaken: {}", "receive_mode_downloads_dir": "De naar je verstuurde bestanden verschijnen in deze map: {}", "receive_mode_warning": "Waarschuwing: Ontvangst mode laat het toe dat mensen bestanden op je computer zetten. Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", "gui_receive_mode_warning": "Ontvangst mode laat het toe dat mensen bestanden op je computer zetten.

    Sommige bestanden kunnen mogelijk de controle over je computer overnemen als je ze opent. Open alleen dingen van mensen die je vertrouwd of als je weet wat je aan het doen bent.", @@ -167,20 +167,23 @@ "gui_settings_receiving_label": "Instellingen voor Ontvangen", "gui_settings_downloads_label": "Sla bestanden op naar", "gui_settings_downloads_button": "Surf", - "gui_settings_public_mode_checkbox": "Publieke mode", + "gui_settings_public_mode_checkbox": "Openbaarmodus", "systray_close_server_title": "OnionShare Server Afgesloten", "systray_close_server_message": "Een gebruiker heeft de server gestopt", "systray_page_loaded_title": "OnionShare Pagina Geladen", "systray_download_page_loaded_message": "Een gebruiker heeft de download pagina geladen", "systray_upload_page_loaded_message": "Een gebruiker heeft de upload pagina geladen", - "gui_uploads": "Upload geschiedenis", - "gui_no_uploads": "Er zijn nog geen uploads", - "gui_clear_history": "Wis alles", - "gui_upload_in_progress": "Upload is gestart{}", + "gui_uploads": "Upload Geschiedenis", + "gui_no_uploads": "Er Zijn Nog Geen Uploads", + "gui_clear_history": "Wis Alles", + "gui_upload_in_progress": "Upload Is Gestart{}", "gui_upload_finished_range": "{} is naar {} gestuurd", "gui_upload_finished": "Verzonden {}", - "gui_download_in_progress": "Binnenhalen gestart {}", - "gui_open_folder_error_nautilus": "Kan de map niet openen omdat bestandsbeheerprogramma nautilus niet beschikbaar is. Het bestand staat hier : {}", + "gui_download_in_progress": "Downloaden Gestart {}", + "gui_open_folder_error_nautilus": "Kan de map niet openen omdat nautilus niet beschikbaar is. Het bestand staat hier: {}", "gui_settings_language_label": "Voorkeurstaal", - "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd." + "gui_settings_language_changed_notice": "Je moet OnionShare opnieuw starten als je de taal veranderd.", + "gui_add_files": "Voeg bestanden toe", + "gui_add_folder": "Voeg map toe", + "gui_connect_to_tor_for_onion_settings": "Verbind met Tor om de instellingen van onion-diensten te zien" } diff --git a/share/locale/no.json b/share/locale/no.json index 381b4e84..073461bb 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -1,6 +1,6 @@ { "give_this_url": "Gi denne adressen til mottakeren:", - "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe serveren", + "ctrlc_to_stop": "Trykk Ctrl+C for å stoppe tjeneren", "not_a_file": "{0:s} er ikke en fil.", "gui_copied_url": "OnionShare-adresse kopiert til utklippstavle", "other_page_loaded": "En annen side har blitt lastet", @@ -186,5 +186,8 @@ "gui_download_in_progress": "Nedlasting startet {}", "gui_settings_language_label": "Foretrukket språk", "gui_settings_language_changed_notice": "Start OnionShare på ny for å se nytt språkvalg.", - "timeout_upload_still_running": "Venter på at opplastingen fullføres" + "timeout_upload_still_running": "Venter på at opplastingen fullføres", + "gui_add_files": "Legg til filer", + "gui_add_folder": "Legg til mappe", + "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger" } diff --git a/share/locale/pl.json b/share/locale/pl.json index d24a0818..f10a30ce 100644 --- a/share/locale/pl.json +++ b/share/locale/pl.json @@ -1,128 +1,128 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", - "timeout_download_still_running": "", - "large_filesize": "", + "config_onion_service": "Konfiguruję usługę onion na porcie {0:d}.", + "preparing_files": "Kompresuję pliki.", + "give_this_url": "Przekaż ten adres odbiorcy:", + "give_this_url_stealth": "Przekaż ten adres i linijkę HidServAuth odbiorcy:", + "give_this_url_receive": "Przekaż ten adres do nadawcy:", + "give_this_url_receive_stealth": "Przekaż ten adres i linijkę HidServAuth do nadawcy:", + "ctrlc_to_stop": "Przyciśnij kombinację klawiszy Ctrl i C aby zatrzymać serwer", + "not_a_file": "{0:s} nie jest prawidłowym plikiem.", + "not_a_readable_file": "{0:s} nie jest plikiem do odczytu.", + "no_available_port": "Nie można znaleźć dostępnego portu aby włączyć usługę onion", + "other_page_loaded": "Adres został wczytany", + "close_on_timeout": "Zatrzymano, gdyż upłynął czas", + "closing_automatically": "Zatrzymano, gdyż pobieranie zostało ukończone", + "timeout_download_still_running": "Czekam na ukończenie pobierania", + "large_filesize": "Uwaga: Wysyłanie dużego pliku może zająć kilka godzin", "systray_menu_exit": "Wyjście", - "systray_download_started_title": "", - "systray_download_started_message": "", - "systray_download_completed_title": "", - "systray_download_completed_message": "", - "systray_download_canceled_title": "", - "systray_download_canceled_message": "", - "systray_upload_started_title": "", - "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", - "gui_add": "", + "systray_download_started_title": "Pobieranie OnionShare zostało rozpoczęte", + "systray_download_started_message": "Użytkownik rozpoczął ściąganie Twoich plików", + "systray_download_completed_title": "Pobieranie OnionShare skończone", + "systray_download_completed_message": "Użytkownik ukończył ściąganie Twoich plików", + "systray_download_canceled_title": "Pobieranie OnionShare zostało anulowane", + "systray_download_canceled_message": "Użytkownik anulował pobieranie", + "systray_upload_started_title": "Wysyłanie OnionShare rozpoczęte", + "systray_upload_started_message": "Użytkownik rozpoczął wysyłanie plików na Twój komputer", + "help_local_only": "Nie wykorzystuj sieci Tor (opcja zaawansowana)", + "help_stay_open": "Kontynuuj udostępnianie po pierwszym pobraniu", + "help_shutdown_timeout": "Przestań udostępniać po określonym czasie w sekundach", + "help_stealth": "Korzystaj z weryfikacji klienta (zaawansowane)", + "help_receive": "Odbieraj dane zamiast je wysyłać", + "help_debug": "Zapisz błędy OnionShare do stdout i zapisz błędy sieciowe na dysku", + "help_filename": "Lista plików i folderów do udostępnienia", + "help_config": "Lokalizacja niestandarowego pliku konfiguracyjnego JSON (opcjonalne)", + "gui_drag_and_drop": "Przeciągnij i upuść pliki i foldery\naby je udostępnić", + "gui_add": "Dodaj", "gui_delete": "Usuń", "gui_choose_items": "Wybierz", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", - "gui_downloads": "", - "gui_no_downloads": "", + "gui_share_start_server": "Rozpocznij udostępnianie", + "gui_share_stop_server": "Zatrzymaj udostępnianie", + "gui_share_stop_server_shutdown_timeout": "Zatrzymaj udostępnianie (zostało {}s)", + "gui_share_stop_server_shutdown_timeout_tooltip": "Czas upłynie za {}", + "gui_receive_start_server": "Rozpocznij tryb odbierania", + "gui_receive_stop_server": "Zatrzymaj tryb odbierania", + "gui_receive_stop_server_shutdown_timeout": "Zatrzymaj tryb odbierania (pozostało {}s)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Czas upływa za {}", + "gui_copy_url": "Kopiuj adres załącznika", + "gui_copy_hidservauth": "Kopiuj HidServAuth", + "gui_downloads": "Historia pobierania", + "gui_no_downloads": "Nie pobrano jeszcze niczego", "gui_canceled": "Anulowano", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "gui_copied_url_title": "Skopiowano adres URL OnionShare", + "gui_copied_url": "Adres URL OnionShare został skopiowany do schowka", + "gui_copied_hidservauth_title": "Skopiowano HidServAuth", + "gui_copied_hidservauth": "Linijka HidServAuth została skopiowana do schowka", + "gui_please_wait": "Rozpoczynam... Kliknij, aby zatrzymać.", + "gui_download_upload_progress_complete": "%p%, {0:s} upłynęło.", + "gui_download_upload_progress_starting": "{0:s}, %p% (obliczam)", + "gui_download_upload_progress_eta": "{0:s}, pozostało: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Nie tak szybko", + "gui_share_quit_warning": "Jesteś w trakcie wysyłania plików. Jesteś pewien, że chcesz wyjść z OnionShare?", + "gui_receive_quit_warning": "Odbierasz teraz pliki. Jesteś pewien, że chcesz wyjść z OnionShare?", "gui_quit_warning_quit": "Wyjście", "gui_quit_warning_dont_quit": "Anuluj", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_rate_limit": "Ktoś zbyt często próbował odczytać Twój adres, co może oznaczać, że ktoś próbuje go odgadnąć, zatem OnionShare zatrzymał serwer. Rozpocznij udostępnianie ponownie i wyślij odbiorcy nowy adres aby udostępniać Twoje pliki.", + "zip_progress_bar_format": "Kompresuję: %p%", + "error_stealth_not_supported": "Aby skorzystać z autoryzacji klienta wymagana jest wersja programu Tor 0.2.9.1-alpha lub nowsza, bądź Tor Browser w wersji 6.5 lub nowszej oraz python3-stem w wersji 1.5 lub nowszej.", + "error_ephemeral_not_supported": "OnionShare wymaga programu Tor w wersji 0.2.7.1 lub nowszej oraz python3-stem w wersji 1.4.0 lub nowszej.", "gui_settings_window_title": "Ustawienia", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "gui_settings_whats_this": "Co to jest?", + "gui_settings_stealth_option": "Użyj autoryzacji klienta", + "gui_settings_stealth_hidservauth_string": "Skoro zapisałeś swój klucz prywatny do ponownego użycia, oznacza to, że możesz\nnacisnąć aby skopiować Twój HidServAuth.", "gui_settings_autoupdate_label": "Sprawdź nową wersję", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_option": "Poinformuj mnie, kiedy nowa wersja programu będzie dostępna", + "gui_settings_autoupdate_timestamp": "Ostatnie sprawdzenie aktualizacji: {}", "gui_settings_autoupdate_timestamp_never": "Nigdy", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "Sprawdź, czy nowa wersja programu jest dostępna", "gui_settings_general_label": "Ustawienia ogólne", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", + "gui_settings_sharing_label": "Ustawienia udostępniania", + "gui_settings_close_after_first_download_option": "Przestań udostępniać po pierwszym pobraniu", + "gui_settings_connection_type_label": "W jaki sposób OnionShare powinien połączyć się z siecią Tor?", + "gui_settings_connection_type_bundled_option": "Skorzystaj z wersji Tora udostępnionego wraz z OnionShare", + "gui_settings_connection_type_automatic_option": "Spróbuj automatycznej konfiguracji za pomocą Tor Browser", "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", + "gui_settings_connection_type_socket_file_option": "Połącz z użyciem pliku socket", + "gui_settings_connection_type_test_button": "Sprawdź połączenie z siecią Tor", "gui_settings_control_port_label": "Port sterowania", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "Plik socket", + "gui_settings_socks_label": "Port SOCKS", + "gui_settings_authenticate_label": "Ustawienia autoryzacji sieci Tor", + "gui_settings_authenticate_no_auth_option": "Brak autoryzacji lub autoryzacji ciasteczek", "gui_settings_authenticate_password_option": "Hasło", "gui_settings_password_label": "Hasło", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges": "Wsparcie mostków sieci Tor", + "gui_settings_tor_bridges_no_bridges_radio_option": "Nie korzystaj z mostków sieci Tor", "gui_settings_tor_bridges_obfs4_radio_option": "", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_meek_lite_expensive_warning": "Uwaga: Mostki meek_lite są bardzo kosztowne dla Tor Project.

    Korzystaj z nich tylko wtedy, gdy nie możesz połączyć się bezpośrednio z siecią Tor, poprzez obsf4 albo przez inne normalne mostki.", + "gui_settings_tor_bridges_custom_radio_option": "Użyj niestandardowych mostków", + "gui_settings_tor_bridges_custom_label": "Mostki możesz znaleźć na https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Żadne z dodanych przez Ciebie mostków nie działają.\nZweryfikuj je lub dodaj inne.", "gui_settings_button_save": "Zapisz", "gui_settings_button_cancel": "Anuluj", "gui_settings_button_help": "Pomoc", "gui_settings_shutdown_timeout_checkbox": "", "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", + "settings_error_unknown": "Nie można połączyć się z kontrolerem Tor, ponieważ Twoje ustawienia nie mają sensu.", + "settings_error_automatic": "Nie można połączyć się z kontrolerem Tor. Czy Tor Browser (dostępny na torproject.org) działa w tle?", + "settings_error_socket_port": "Nie można połączyć się z kontrolerem Tor pod adresem {}:{}.", + "settings_error_socket_file": "Nie można połączyć się z kontrolerem Tor używając pliku socket znajdującym się w ścieżce {}.", + "settings_error_auth": "Połączono z {}:{} ale nie można uwierzytelnić. Być może to nie jest kontroler Tor?", + "settings_error_missing_password": "Połączono z kontrolerem Tor ale wymaga on hasła do uwierzytelnienia.", + "settings_error_unreadable_cookie_file": "Połączono z kontrolerem Tor ale hasło może być niepoprawne albo Twój użytkownik nie ma uprawnień do odczytania plików cookie.", "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", + "settings_error_bundled_tor_timeout": "Połączenie się z siecią Tor zajmuje zbyt dużo czasu. Być może nie jesteś połączony z internetem albo masz niedokładny zegar systemowy?", + "settings_error_bundled_tor_broken": "OnionShare nie mógł połączyć się z siecią Tor w tle\n{}", + "settings_test_success": "Połączono z kontrolerem Tor.\n\nWersja Tor: {}\nWsparcie ulotnych serwisów onion: {}.\nWsparcie autoryzacji klienta: {}.\nWsparcie adresów onion nowej generacji: {}.", + "error_tor_protocol_error": "Pojawił się błąd z Tor: {}", + "error_tor_protocol_error_unknown": "Pojawił się nieznany błąd z Tor", + "error_invalid_private_key": "Ten typ klucza prywatnego jest niewspierany", + "connecting_to_tor": "Łączę z siecią Tor", + "update_available": "Nowa wersja programu OnionShare jest dostępna. Naciśnij tutaj aby ją ściągnąć.

    Korzystasz z wersji {} a najnowszą jest {}.", + "update_error_check_error": "Nie można sprawdzić czy są dostępne aktualizacje. Strona programu OnionShare mówi, że ostatnia wersja programu jest nierozpoznawalna '{}'…", "update_error_invalid_latest_version": "", "update_not_available": "", "gui_tor_connection_ask": "", @@ -181,5 +181,8 @@ "gui_download_in_progress": "", "gui_open_folder_error_nautilus": "", "gui_settings_language_label": "Preferowany język", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "", + "timeout_upload_still_running": "Czekam na ukończenie wysyłania", + "gui_add_files": "Dodaj pliki", + "gui_add_folder": "Dodaj foldery" } diff --git a/share/locale/sn.json b/share/locale/sn.json new file mode 100644 index 00000000..11c255f6 --- /dev/null +++ b/share/locale/sn.json @@ -0,0 +1,188 @@ +{ + "config_onion_service": "", + "preparing_files": "", + "give_this_url": "", + "give_this_url_stealth": "", + "give_this_url_receive": "", + "give_this_url_receive_stealth": "", + "ctrlc_to_stop": "", + "not_a_file": "", + "not_a_readable_file": "", + "no_available_port": "", + "other_page_loaded": "", + "close_on_timeout": "", + "closing_automatically": "", + "timeout_download_still_running": "", + "timeout_upload_still_running": "", + "large_filesize": "", + "systray_menu_exit": "", + "systray_download_started_title": "", + "systray_download_started_message": "", + "systray_download_completed_title": "", + "systray_download_completed_message": "", + "systray_download_canceled_title": "", + "systray_download_canceled_message": "", + "systray_upload_started_title": "", + "systray_upload_started_message": "", + "help_local_only": "", + "help_stay_open": "", + "help_shutdown_timeout": "", + "help_stealth": "", + "help_receive": "", + "help_debug": "", + "help_filename": "", + "help_config": "", + "gui_drag_and_drop": "", + "gui_add": "", + "gui_add_files": "", + "gui_add_folder": "", + "gui_delete": "", + "gui_choose_items": "", + "gui_share_start_server": "", + "gui_share_stop_server": "", + "gui_share_stop_server_shutdown_timeout": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_start_server": "", + "gui_receive_stop_server": "", + "gui_receive_stop_server_shutdown_timeout": "", + "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_copy_url": "", + "gui_copy_hidservauth": "", + "gui_downloads": "", + "gui_no_downloads": "", + "gui_canceled": "", + "gui_copied_url_title": "", + "gui_copied_url": "", + "gui_copied_hidservauth_title": "", + "gui_copied_hidservauth": "", + "gui_please_wait": "", + "gui_download_upload_progress_complete": "", + "gui_download_upload_progress_starting": "", + "gui_download_upload_progress_eta": "", + "version_string": "", + "gui_quit_title": "", + "gui_share_quit_warning": "", + "gui_receive_quit_warning": "", + "gui_quit_warning_quit": "", + "gui_quit_warning_dont_quit": "", + "error_rate_limit": "", + "zip_progress_bar_format": "", + "error_stealth_not_supported": "", + "error_ephemeral_not_supported": "", + "gui_settings_window_title": "", + "gui_settings_whats_this": "", + "gui_settings_stealth_option": "", + "gui_settings_stealth_hidservauth_string": "", + "gui_settings_autoupdate_label": "", + "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp_never": "", + "gui_settings_autoupdate_check_button": "", + "gui_settings_general_label": "", + "gui_settings_sharing_label": "", + "gui_settings_close_after_first_download_option": "", + "gui_settings_connection_type_label": "", + "gui_settings_connection_type_bundled_option": "", + "gui_settings_connection_type_automatic_option": "", + "gui_settings_connection_type_control_port_option": "", + "gui_settings_connection_type_socket_file_option": "", + "gui_settings_connection_type_test_button": "", + "gui_settings_control_port_label": "", + "gui_settings_socket_file_label": "", + "gui_settings_socks_label": "", + "gui_settings_authenticate_label": "", + "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_password_option": "", + "gui_settings_password_label": "", + "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges_no_bridges_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option": "", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", + "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_custom_radio_option": "", + "gui_settings_tor_bridges_custom_label": "", + "gui_settings_tor_bridges_invalid": "", + "gui_settings_button_save": "", + "gui_settings_button_cancel": "", + "gui_settings_button_help": "", + "gui_settings_shutdown_timeout_checkbox": "", + "gui_settings_shutdown_timeout": "", + "settings_error_unknown": "", + "settings_error_automatic": "", + "settings_error_socket_port": "", + "settings_error_socket_file": "", + "settings_error_auth": "", + "settings_error_missing_password": "", + "settings_error_unreadable_cookie_file": "", + "settings_error_bundled_tor_not_supported": "", + "settings_error_bundled_tor_timeout": "", + "settings_error_bundled_tor_broken": "", + "settings_test_success": "", + "error_tor_protocol_error": "", + "error_tor_protocol_error_unknown": "", + "error_invalid_private_key": "", + "connecting_to_tor": "", + "update_available": "", + "update_error_check_error": "", + "update_error_invalid_latest_version": "", + "update_not_available": "", + "gui_tor_connection_ask": "", + "gui_tor_connection_ask_open_settings": "", + "gui_tor_connection_ask_quit": "", + "gui_tor_connection_error_settings": "", + "gui_tor_connection_canceled": "", + "gui_tor_connection_lost": "", + "gui_server_started_after_timeout": "", + "gui_server_timeout_expired": "", + "share_via_onionshare": "", + "gui_connect_to_tor_for_onion_settings": "", + "gui_use_legacy_v2_onions_checkbox": "", + "gui_save_private_key_checkbox": "", + "gui_share_url_description": "", + "gui_receive_url_description": "", + "gui_url_label_persistent": "", + "gui_url_label_stay_open": "", + "gui_url_label_onetime": "", + "gui_url_label_onetime_and_persistent": "", + "gui_status_indicator_share_stopped": "", + "gui_status_indicator_share_working": "", + "gui_status_indicator_share_started": "", + "gui_status_indicator_receive_stopped": "", + "gui_status_indicator_receive_working": "", + "gui_status_indicator_receive_started": "", + "gui_file_info": "", + "gui_file_info_single": "", + "history_in_progress_tooltip": "", + "history_completed_tooltip": "", + "info_in_progress_uploads_tooltip": "", + "info_completed_uploads_tooltip": "", + "error_cannot_create_downloads_dir": "", + "receive_mode_downloads_dir": "", + "receive_mode_warning": "", + "gui_receive_mode_warning": "", + "receive_mode_upload_starting": "", + "receive_mode_received_file": "", + "gui_mode_share_button": "", + "gui_mode_receive_button": "", + "gui_settings_receiving_label": "", + "gui_settings_downloads_label": "", + "gui_settings_downloads_button": "", + "gui_settings_public_mode_checkbox": "", + "systray_close_server_title": "", + "systray_close_server_message": "", + "systray_page_loaded_title": "", + "systray_download_page_loaded_message": "", + "systray_upload_page_loaded_message": "", + "gui_uploads": "", + "gui_no_uploads": "", + "gui_clear_history": "", + "gui_upload_in_progress": "", + "gui_upload_finished_range": "", + "gui_upload_finished": "", + "gui_download_in_progress": "", + "gui_open_folder_error_nautilus": "", + "gui_settings_language_label": "", + "gui_settings_language_changed_notice": "" +} diff --git a/share/locale/sv.json b/share/locale/sv.json index c5f6e8da..c0e28ec7 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -10,7 +10,7 @@ "not_a_readable_file": "{0:s} är inte en läsbar fil.", "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", "other_page_loaded": "Adress laddad", - "close_on_timeout": "", + "close_on_timeout": "Stoppad för att automatiska stopp-timern tiden tog slut", "closing_automatically": "Stannade för att nedladdningen blev klar", "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", @@ -39,7 +39,7 @@ "gui_share_start_server": "Påbörja delning", "gui_share_stop_server": "Avbryt delning", "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", - "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern slutar vid {}", "gui_receive_start_server": "Starta Mottagarläge", "gui_receive_stop_server": "Avsluta Mottagarläge", "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", @@ -52,134 +52,137 @@ "gui_copied_url_title": "OnionShare Adress Kopierad", "gui_copied_url": "OnionShare adress kopierad till urklipp", "gui_copied_hidservauth_title": "HidServAuth Kopierad", - "gui_copied_hidservauth": "", - "gui_please_wait": "", - "gui_download_upload_progress_complete": "", - "gui_download_upload_progress_starting": "", - "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", - "gui_quit_warning_quit": "", - "gui_quit_warning_dont_quit": "", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", - "gui_settings_authenticate_password_option": "", - "gui_settings_password_label": "", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", - "gui_settings_button_save": "", - "gui_settings_button_cancel": "", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_copied_hidservauth": "HidServAuth-rad kopierad till urklipp", + "gui_please_wait": "Börjar... klicka för att avbryta.", + "gui_download_upload_progress_complete": "%p%, {0:s} förflutit.", + "gui_download_upload_progress_starting": "{0:s}, %p% (beräknar)", + "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "version_string": "OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "Inte så fort", + "gui_share_quit_warning": "Du håller på att skicka filer. Är du säker på att du vill avsluta OnionShare?", + "gui_receive_quit_warning": "Du håller på att ta emot filer. Är du säker på att du vill avsluta OnionShare?", + "gui_quit_warning_quit": "Avsluta", + "gui_quit_warning_dont_quit": "Avbryt", + "error_rate_limit": "Någon har gjort för många felaktiga försök på din adress, vilket innebär att de kan försöka gissa det, så OnionShare har stoppat servern. Börja dela igen och skicka mottagaren en ny adress att dela.", + "zip_progress_bar_format": "Komprimerar: %p%", + "error_stealth_not_supported": "För att använda klientauktorisering behöver du minst både Tor 0.2.9.1-alpha (eller Tor Browser 6.5) och python3-stem 1.5.0.", + "error_ephemeral_not_supported": "OnionShare kräver minst både Tor 0.2.7.1 och python3-stem 1.4.0.", + "gui_settings_window_title": "Inställningar", + "gui_settings_whats_this": "Vad är det här?", + "gui_settings_stealth_option": "Använd klientauktorisering", + "gui_settings_stealth_hidservauth_string": "Efter att ha sparat din privata nyckel för återanvändning, innebär att du kan nu\nklicka för att kopiera din HidServAuth.", + "gui_settings_autoupdate_label": "Sök efter ny version", + "gui_settings_autoupdate_option": "Meddela mig när en ny version är tillgänglig", + "gui_settings_autoupdate_timestamp": "Senast kontrollerad: {}", + "gui_settings_autoupdate_timestamp_never": "Aldrig", + "gui_settings_autoupdate_check_button": "Sök efter ny version", + "gui_settings_general_label": "Allmänna inställningar", + "gui_settings_sharing_label": "Delningsinställningar", + "gui_settings_close_after_first_download_option": "Sluta dela efter första hämtningen", + "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", + "gui_settings_connection_type_bundled_option": "Använd Tor-versionen inbyggd i OnionShare", + "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", + "gui_settings_connection_type_control_port_option": "Anslut med kontrollport", + "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", + "gui_settings_connection_type_test_button": "Provningsanslutning till Tor", + "gui_settings_control_port_label": "Kontrollport", + "gui_settings_socket_file_label": "Socket-fil", + "gui_settings_socks_label": "SOCKS-port", + "gui_settings_authenticate_label": "Tor-autentiseringsinställningar", + "gui_settings_authenticate_no_auth_option": "Ingen autentisering eller kak-autentisering", + "gui_settings_authenticate_password_option": "Lösenord", + "gui_settings_password_label": "Lösenord", + "gui_settings_tor_bridges": "Stöd för Tor broar", + "gui_settings_tor_bridges_no_bridges_radio_option": "Använd inte broar", + "gui_settings_tor_bridges_obfs4_radio_option": "Använd inbyggda obfs4 pluggbara transporter", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Använd inbyggda obfs4 pluggbara transporter (kräver obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Använd inbyggda meek_lite (Azure) pluggbara transporter", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Använd inbyggda meek_lite (Azure) pluggbara transporter (kräver obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Varning: meek_lite-broarna är mycket kostsamma för att Tor-projektet ska kunna köras.

    Använd dem endast om det inte går att ansluta till Tor direkt, via obfs4-transporter eller andra normala broar.", + "gui_settings_tor_bridges_custom_radio_option": "Använd anpassade broar", + "gui_settings_tor_bridges_custom_label": "Du kan få broar från https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ingen av broarna du lagt till arbete.\nDubbelkolla dem eller lägga till andra.", + "gui_settings_button_save": "Spara", + "gui_settings_button_cancel": "Avbryt", + "gui_settings_button_help": "Hjälp", + "gui_settings_shutdown_timeout_checkbox": "Använd automatiska stopp-timern", + "gui_settings_shutdown_timeout": "Stoppa delningen vid:", + "settings_error_unknown": "Kan inte ansluta till Tor-regulatorn eftersom dina inställningar inte är vettiga.", + "settings_error_automatic": "Kunde inte ansluta till Tor-regulatorn. Körs Tor Browser (tillgänglig från torproject.org) i bakgrunden?", + "settings_error_socket_port": "Det går inte att ansluta till Tor-regulatorn på {}:{}.", + "settings_error_socket_file": "Det går inte att ansluta till Tor-regulatorn med socket-filen {}.", + "settings_error_auth": "Ansluten till {}:{}, men kan inte autentisera. Kanske är det här inte en Tor-regulator?", + "settings_error_missing_password": "Ansluten till Tor-regulatorn, men den kräver ett lösenord för att autentisera.", + "settings_error_unreadable_cookie_file": "Ansluten till Tor-regulatorn, men lösenordet kan vara fel, eller din användare är inte tillåtet att läsa kakfilen.", + "settings_error_bundled_tor_not_supported": "Användning av Tor-versionen som följer med OnionShare fungerar inte i utvecklarläge på Windows eller macOS.", + "settings_error_bundled_tor_timeout": "Det tar för lång tid att ansluta till Tor. Kanske är du inte ansluten till Internet, eller har en felaktig systemklocka?", + "settings_error_bundled_tor_broken": "OnionShare kunde inte ansluta till Tor i bakgrunden:\n{}", + "settings_test_success": "Ansluten till Tor-regulatorn.\n\nTor version: {}\nStöder efemära onion-tjänster: {}.\nStöder klientautentisering: {}.\nStöder nästa generations .onion-adresser: {}.", + "error_tor_protocol_error": "Det fanns ett fel med Tor: {}", + "error_tor_protocol_error_unknown": "Det fanns ett okänt fel med Tor", + "error_invalid_private_key": "Denna privata nyckeltyp stöds inte", + "connecting_to_tor": "Ansluter till Tor-nätverket", + "update_available": "Ny OnionShare utgiven. Klicka här för att få det.

    Du använder {} och det senaste är {}.", + "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", + "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: kanske är du inte ansluten till Tor, eller OnionShare-webbplatsen är nere?", + "update_not_available": "Du kör den senaste OnionShare.", + "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?", + "gui_tor_connection_ask_open_settings": "Ja", + "gui_tor_connection_ask_quit": "Avsluta", + "gui_tor_connection_error_settings": "Försök ändra hur OnionShare ansluter till Tor-nätverket i inställningarna.", + "gui_tor_connection_canceled": "Kunde inte ansluta till Tor.\n\nSe till att du är ansluten till Internet, öppna sedan OnionShare och ställ in anslutningen till Tor.", + "gui_tor_connection_lost": "Frånkopplad från Tor.", + "gui_server_started_after_timeout": "Automatiska stopp-timern tog slut innan servern startade.\nVänligen gör en ny delning.", + "gui_server_timeout_expired": "Automatiska stopp-timern har redan slutat.\nUppdatera den för att börja dela.", + "share_via_onionshare": "OnionShare den", + "gui_use_legacy_v2_onions_checkbox": "Använd äldre adresser", + "gui_save_private_key_checkbox": "Använd en beständig adress", + "gui_share_url_description": "Alla med denna OnionShare-adress kan hämta dina filer med hjälp av Tor Browser: ", + "gui_receive_url_description": "Alla med denna OnionShare-adress kan skicka filer till din dator med hjälp av Tor Browser: ", + "gui_url_label_persistent": "Denna delning kommer inte automatiskt att sluta.
    < br>Varje efterföljande delning återanvänder adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)", + "gui_url_label_stay_open": "Denna delning kommer inte automatiskt att sluta.", + "gui_url_label_onetime": "Denna delning kommer att sluta efter första slutförandet.", + "gui_url_label_onetime_and_persistent": "Denna delning kommer inte automatiskt att sluta.
    < br>Varje efterföljande delning kommer att återanvända adressen. (För att använda engångsadresser, stäng av \"använd beständig adress\" i inställningarna.)", + "gui_status_indicator_share_stopped": "Redo att dela", + "gui_status_indicator_share_working": "Börjar…", + "gui_status_indicator_share_started": "Delar", + "gui_status_indicator_receive_stopped": "Redo att ta emot", + "gui_status_indicator_receive_working": "Börjar…", + "gui_status_indicator_receive_started": "Tar emot", + "gui_file_info": "{} filer, {}", + "gui_file_info_single": "{} fil, {}", + "history_in_progress_tooltip": "{} pågår", + "history_completed_tooltip": "{} slutförda", + "info_in_progress_uploads_tooltip": "{} pågående sändning(ar)", + "info_completed_uploads_tooltip": "{} sändning(ar) slutförd(a)", + "error_cannot_create_downloads_dir": "Det gick inte att skapa mappen mottagningsläge: {}", + "receive_mode_downloads_dir": "Filer som skickas till dig visas i den här mappen: {}", + "receive_mode_warning": "Varning: Mottagningsläge låter personer skicka filer till din dator. Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", + "gui_receive_mode_warning": "Mottagningsläge låter personer skicka filer till din dator.

    Vissa filer kan potentiellt ta kontroll över din dator om du öppnar dem. Bara öppna saker från personer du litar på, eller om du vet vad du gör.", + "receive_mode_upload_starting": "Sändning av total storlek {} börjar", + "receive_mode_received_file": "Mottaget: {}", + "gui_mode_share_button": "Dela filer", + "gui_mode_receive_button": "Ta emot filer", + "gui_settings_receiving_label": "Mottagning-inställningar", + "gui_settings_downloads_label": "Spara filer till", + "gui_settings_downloads_button": "Bläddra", + "gui_settings_public_mode_checkbox": "Offentligt läge", + "systray_close_server_title": "OnionShare-servern stängd", + "systray_close_server_message": "En användare stängde servern", + "systray_page_loaded_title": "OnionShare-sidan lästes in", + "systray_download_page_loaded_message": "En användare läste in hämtningssidan", + "systray_upload_page_loaded_message": "En användare läste in sändningssidan", + "gui_uploads": "Sändningshistoriken", + "gui_no_uploads": "Inga sändningar ännu", + "gui_clear_history": "Rensa alla", + "gui_upload_in_progress": "Sändning påbörjad {}", + "gui_upload_finished_range": "Skickade {} till {}", + "gui_upload_finished": "Skickade {}", + "gui_download_in_progress": "Hämtning påbörjad {}", + "gui_open_folder_error_nautilus": "Det går inte att öppna mappen eftersom nautilus inte är tillgänglig. Filen är här: {}", + "gui_settings_language_label": "Föredraget språk", + "gui_settings_language_changed_notice": "Starta om OnionShare för att din språkändring ska träda i kraft.", + "gui_add_files": "Lägg till filer", + "gui_add_folder": "Lägg till mapp", + "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar" } -- cgit v1.2.3-54-g00ecf From aa200c012cac01b8c8d09f93cca713866a6757d0 Mon Sep 17 00:00:00 2001 From: Taro Tanaka Date: Sun, 20 Jan 2019 05:12:37 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index acddc8c2..e846a5f0 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -109,9 +109,9 @@ "gui_settings_button_help": "ヘルプ", "gui_settings_shutdown_timeout_checkbox": "自動停止タイマーを使用する", "gui_settings_shutdown_timeout": "共有を停止する時間:", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", + "settings_error_unknown": "設定を解釈できないため、Torコントローラーと接続できません。", + "settings_error_automatic": "Torコントローラーと接続できません。Torブラウザ(torproject.orgから入手できる)がバックグラウンドで動作していますか?", + "settings_error_socket_port": "{}:{}でTorコントローラーと接続できません。", "settings_error_socket_file": "", "settings_error_auth": "", "settings_error_missing_password": "", -- cgit v1.2.3-54-g00ecf From 6e3b103ef51fb5faa891cd90c7ffd8bbfca16d54 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 11:46:20 -0800 Subject: Rename receive mode "downloads_dir" to the OnionShare "data_dir" --- onionshare/__init__.py | 2 +- onionshare/settings.py | 8 ++++---- onionshare/web/receive_mode.py | 6 +++--- onionshare/web/web.py | 2 +- onionshare_gui/onionshare_gui.py | 4 ++-- onionshare_gui/settings_dialog.py | 36 ++++++++++++++++++------------------ share/locale/en.json | 8 ++++---- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/onionshare/__init__.py b/onionshare/__init__.py index 0d064639..2f44c846 100644 --- a/onionshare/__init__.py +++ b/onionshare/__init__.py @@ -175,7 +175,7 @@ def main(cwd=None): print('') if mode == 'receive': - print(strings._('receive_mode_downloads_dir').format(common.settings.get('downloads_dir'))) + print(strings._('receive_mode_data_dir').format(common.settings.get('data_dir'))) print('') print(strings._('receive_mode_warning')) print('') diff --git a/onionshare/settings.py b/onionshare/settings.py index 06235198..1570a150 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -100,7 +100,7 @@ class Settings(object): 'public_mode': False, 'slug': '', 'hidservauth_string': '', - 'downloads_dir': self.build_default_downloads_dir(), + 'data_dir': self.build_default_data_dir(), 'locale': None # this gets defined in fill_in_defaults() } self._settings = {} @@ -140,7 +140,7 @@ class Settings(object): """ return os.path.join(self.common.build_data_dir(), 'onionshare.json') - def build_default_downloads_dir(self): + def build_default_data_dir(self): """ Returns the path of the default Downloads directory for receive mode. """ @@ -174,9 +174,9 @@ class Settings(object): except: pass - # Make sure downloads_dir exists + # Make sure data_dir exists try: - os.makedirs(self.get('downloads_dir'), exist_ok=True) + os.makedirs(self.get('data_dir'), exist_ok=True) except: pass diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 6985f38a..2b4e8faf 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -64,15 +64,15 @@ class ReceiveModeWeb(object): now = datetime.now() date_dir = now.strftime("%Y-%m-%d") time_dir = now.strftime("%H.%M.%S") - receive_mode_dir = os.path.join(self.common.settings.get('downloads_dir'), date_dir, time_dir) + receive_mode_dir = os.path.join(self.common.settings.get('data_dir'), date_dir, time_dir) valid = True try: os.makedirs(receive_mode_dir, 0o700) except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path, { + self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { "receive_mode_dir": receive_mode_dir }) - print(strings._('error_cannot_create_downloads_dir').format(receive_mode_dir)) + print(strings._('error_cannot_create_data_dir').format(receive_mode_dir)) valid = False if not valid: flash('Error uploading, please inform the OnionShare user', 'error') diff --git a/onionshare/web/web.py b/onionshare/web/web.py index df838df7..c0e9d6b6 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -38,7 +38,7 @@ class Web(object): REQUEST_UPLOAD_FILE_RENAMED = 6 REQUEST_UPLOAD_SET_DIR = 7 REQUEST_UPLOAD_FINISHED = 8 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 9 + REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 9 def __init__(self, common, is_gui, mode='share'): self.common = common diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index 3d5f5f0a..710a1d84 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -393,8 +393,8 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FINISHED: mode.handle_request_upload_finished(event) - if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: - Alert(self.common, strings._('error_cannot_create_downloads_dir').format(event["data"]["receive_mode_dir"])) + if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE: + Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"])) if event["type"] == Web.REQUEST_OTHER: if event["path"] != '/favicon.ico' and event["path"] != "/{}/shutdown".format(mode.web.shutdown_slug): diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index b933c30f..aa94b551 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -187,16 +187,16 @@ class SettingsDialog(QtWidgets.QDialog): sharing_group = QtWidgets.QGroupBox(strings._("gui_settings_sharing_label")) sharing_group.setLayout(sharing_group_layout) - # Downloads dir - downloads_label = QtWidgets.QLabel(strings._('gui_settings_downloads_label')); - self.downloads_dir_lineedit = QtWidgets.QLineEdit() - self.downloads_dir_lineedit.setReadOnly(True) - downloads_button = QtWidgets.QPushButton(strings._('gui_settings_downloads_button')) - downloads_button.clicked.connect(self.downloads_button_clicked) + # OnionShare data dir + data_dir_label = QtWidgets.QLabel(strings._('gui_settings_data_dir_label')); + self.data_dir_lineedit = QtWidgets.QLineEdit() + self.data_dir_lineedit.setReadOnly(True) + data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button')) + data_dir_button.clicked.connect(self.data_dir_button_clicked) downloads_layout = QtWidgets.QHBoxLayout() - downloads_layout.addWidget(downloads_label) - downloads_layout.addWidget(self.downloads_dir_lineedit) - downloads_layout.addWidget(downloads_button) + downloads_layout.addWidget(data_dir_label) + downloads_layout.addWidget(self.data_dir_lineedit) + downloads_layout.addWidget(data_dir_button) # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() @@ -508,8 +508,8 @@ class SettingsDialog(QtWidgets.QDialog): if use_legacy_v2_onions or save_private_key: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) - downloads_dir = self.old_settings.get('downloads_dir') - self.downloads_dir_lineedit.setText(downloads_dir) + data_dir = self.old_settings.get('data_dir') + self.data_dir_lineedit.setText(data_dir) public_mode = self.old_settings.get('public_mode') if public_mode: @@ -747,17 +747,17 @@ class SettingsDialog(QtWidgets.QDialog): if not self.save_private_key_checkbox.isChecked(): self.use_legacy_v2_onions_checkbox.setEnabled(True) - def downloads_button_clicked(self): + def data_dir_button_clicked(self): """ - Browse for a new downloads directory + Browse for a new OnionShare data directory """ - downloads_dir = self.downloads_dir_lineedit.text() + data_dir = self.data_dir_lineedit.text() selected_dir = QtWidgets.QFileDialog.getExistingDirectory(self, - strings._('gui_settings_downloads_label'), downloads_dir) + strings._('gui_settings_data_dir_label'), data_dir) if selected_dir: - self.common.log('SettingsDialog', 'downloads_button_clicked', 'selected dir: {}'.format(selected_dir)) - self.downloads_dir_lineedit.setText(selected_dir) + self.common.log('SettingsDialog', 'data_dir_button_clicked', 'selected dir: {}'.format(selected_dir)) + self.data_dir_lineedit.setText(selected_dir) def test_tor_clicked(self): """ @@ -981,7 +981,7 @@ class SettingsDialog(QtWidgets.QDialog): # Also unset the HidServAuth if we are removing our reusable private key settings.set('hidservauth_string', '') - settings.set('downloads_dir', self.downloads_dir_lineedit.text()) + settings.set('data_dir', self.data_dir_lineedit.text()) settings.set('public_mode', self.public_mode_checkbox.isChecked()) settings.set('use_stealth', self.stealth_checkbox.isChecked()) # Always unset the HidServAuth if Stealth mode is unset diff --git a/share/locale/en.json b/share/locale/en.json index 8bcc738b..a3307e49 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -140,8 +140,8 @@ "gui_file_info_single": "{} file, {}", "history_in_progress_tooltip": "{} in progress", "history_completed_tooltip": "{} completed", - "error_cannot_create_downloads_dir": "Could not create receive mode folder: {}", - "receive_mode_downloads_dir": "Files sent to you appear in this folder: {}", + "error_cannot_create_data_dir": "Could not create OnionShare data folder: {}", + "receive_mode_data_dir": "Files sent to you appear in this folder: {}", "receive_mode_warning": "Warning: Receive mode lets people upload files to your computer. Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", "gui_receive_mode_warning": "Receive mode lets people upload files to your computer.

    Some files can potentially take control of your computer if you open them. Only open things from people you trust, or if you know what you are doing.", "receive_mode_upload_starting": "Upload of total size {} is starting", @@ -149,8 +149,8 @@ "gui_mode_share_button": "Share Files", "gui_mode_receive_button": "Receive Files", "gui_settings_receiving_label": "Receiving settings", - "gui_settings_downloads_label": "Save files to", - "gui_settings_downloads_button": "Browse", + "gui_settings_data_dir_label": "Save files to", + "gui_settings_data_dir_browse_button": "Browse", "gui_settings_public_mode_checkbox": "Public mode", "gui_open_folder_error_nautilus": "Cannot open folder because nautilus is not available. The file is here: {}", "gui_settings_language_label": "Preferred language", -- cgit v1.2.3-54-g00ecf From 53ec2176c1efe3faaf8b0eaf774ce756f33ed8a7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 12:01:51 -0800 Subject: Fix tests to use data_dir setting instead of downloads_dir --- tests/GuiBaseTest.py | 50 +++++++++++++++++++-------------------- tests/GuiReceiveTest.py | 2 +- tests/SettingsGuiBaseTest.py | 4 ++-- tests/TorGuiBaseTest.py | 2 +- tests/test_onionshare_settings.py | 2 +- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/tests/GuiBaseTest.py b/tests/GuiBaseTest.py index c557fc15..e4b3d4c9 100644 --- a/tests/GuiBaseTest.py +++ b/tests/GuiBaseTest.py @@ -39,7 +39,7 @@ class GuiBaseTest(object): strings.load_strings(common) # Get all of the settings in test_settings - test_settings['downloads_dir'] = '/tmp/OnionShare' + test_settings['data_dir'] = '/tmp/OnionShare' for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val @@ -70,17 +70,17 @@ class GuiBaseTest(object): except: pass - + def gui_loaded(self): '''Test that the GUI actually is shown''' self.assertTrue(self.gui.show) - + def windowTitle_seen(self): '''Test that the window title is OnionShare''' self.assertEqual(self.gui.windowTitle(), 'OnionShare') - + def settings_button_is_visible(self): '''Test that the settings button is visible''' self.assertTrue(self.gui.settings_button.isVisible()) @@ -90,12 +90,12 @@ class GuiBaseTest(object): '''Test that the settings button is hidden when the server starts''' self.assertFalse(self.gui.settings_button.isVisible()) - + def server_status_bar_is_visible(self): '''Test that the status bar is visible''' self.assertTrue(self.gui.status_bar.isVisible()) - + def click_mode(self, mode): '''Test that we can switch Mode by clicking the button''' if type(mode) == ReceiveMode: @@ -105,14 +105,14 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(self.gui.share_mode_button, QtCore.Qt.LeftButton) self.assertTrue(self.gui.mode, self.gui.MODE_SHARE) - + def click_toggle_history(self, mode): '''Test that we can toggle Download or Upload history by clicking the toggle button''' currently_visible = mode.history.isVisible() QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertEqual(mode.history.isVisible(), not currently_visible) - + def history_indicator(self, mode, public_mode): '''Test that we can make sure the history is toggled off, do an action, and the indiciator works''' # Make sure history is toggled off @@ -150,43 +150,43 @@ class GuiBaseTest(object): QtTest.QTest.mouseClick(mode.toggle_history, QtCore.Qt.LeftButton) self.assertFalse(mode.toggle_history.indicator_label.isVisible()) - + def history_is_not_visible(self, mode): '''Test that the History section is not visible''' self.assertFalse(mode.history.isVisible()) - + def history_is_visible(self, mode): '''Test that the History section is visible''' self.assertTrue(mode.history.isVisible()) - + def server_working_on_start_button_pressed(self, mode): '''Test we can start the service''' # Should be in SERVER_WORKING state QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 1) - + def server_status_indicator_says_starting(self, mode): '''Test that the Server Status indicator shows we are Starting''' self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_working')) - + def server_is_started(self, mode, startup_time=2000): '''Test that the server has started''' QtTest.QTest.qWait(startup_time) # Should now be in SERVER_STARTED state self.assertEqual(mode.server_status.status, 2) - + def web_server_is_running(self): '''Test that the web server has started''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - + def have_a_slug(self, mode, public_mode): '''Test that we have a valid slug''' if not public_mode: @@ -194,12 +194,12 @@ class GuiBaseTest(object): else: self.assertIsNone(mode.server_status.web.slug, r'(\w+)-(\w+)') - + def url_description_shown(self, mode): '''Test that the URL label is showing''' self.assertTrue(mode.server_status.url_description.isVisible()) - + def have_copy_url_button(self, mode, public_mode): '''Test that the Copy URL button is shown and that the clipboard is correct''' self.assertTrue(mode.server_status.copy_url_button.isVisible()) @@ -211,7 +211,7 @@ class GuiBaseTest(object): else: self.assertEqual(clipboard.text(), 'http://127.0.0.1:{}/{}'.format(self.gui.app.port, mode.server_status.web.slug)) - + def server_status_indicator_says_started(self, mode): '''Test that the Server Status indicator shows we are started''' if type(mode) == ReceiveMode: @@ -219,7 +219,7 @@ class GuiBaseTest(object): if type(mode) == ShareMode: self.assertEqual(mode.server_status_label.text(), strings._('gui_status_indicator_share_started')) - + def web_page(self, mode, string, public_mode): '''Test that the web page contains a string''' s = socks.socksocket() @@ -248,25 +248,25 @@ class GuiBaseTest(object): self.assertTrue(string in f.read()) f.close() - + def history_widgets_present(self, mode): '''Test that the relevant widgets are present in the history view after activity has taken place''' self.assertFalse(mode.history.empty.isVisible()) self.assertTrue(mode.history.not_empty.isVisible()) - + def counter_incremented(self, mode, count): '''Test that the counter has incremented''' self.assertEqual(mode.history.completed_count, count) - + def server_is_stopped(self, mode, stay_open): '''Test that the server stops when we click Stop''' if type(mode) == ReceiveMode or (type(mode) == ShareMode and stay_open): QtTest.QTest.mouseClick(mode.server_status.server_button, QtCore.Qt.LeftButton) self.assertEqual(mode.server_status.status, 0) - + def web_server_is_stopped(self): '''Test that the web server also stopped''' QtTest.QTest.qWait(2000) @@ -275,7 +275,7 @@ class GuiBaseTest(object): # We should be closed by now. Fail if not! self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) - + def server_status_indicator_says_closed(self, mode, stay_open): '''Test that the Server Status indicator shows we closed''' if type(mode) == ReceiveMode: @@ -319,5 +319,3 @@ class GuiBaseTest(object): self.windowTitle_seen() self.settings_button_is_visible() self.server_status_bar_is_visible() - - diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index eaed8343..a23a4bc6 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -21,7 +21,7 @@ class GuiReceiveTest(GuiBaseTest): for i in range(10): date_dir = now.strftime("%Y-%m-%d") time_dir = now.strftime("%H.%M.%S") - receive_mode_dir = os.path.join(self.gui.common.settings.get('downloads_dir'), date_dir, time_dir) + receive_mode_dir = os.path.join(self.gui.common.settings.get('data_dir'), date_dir, time_dir) expected_filename = os.path.join(receive_mode_dir, expected_basename) if os.path.exists(expected_filename): exists = True diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 47d698f3..71244e0f 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -148,7 +148,7 @@ class SettingsGuiBaseTest(object): self.assertFalse(self.gui.close_after_first_download_checkbox.isChecked()) # receive mode - self.gui.downloads_dir_lineedit.setText('/tmp/OnionShareSettingsTest') + self.gui.data_dir_lineedit.setText('/tmp/OnionShareSettingsTest') # bundled mode is enabled @@ -234,7 +234,7 @@ class SettingsGuiBaseTest(object): self.assertFalse(data["save_private_key"]) self.assertFalse(data["use_stealth"]) - self.assertEqual(data["downloads_dir"], "/tmp/OnionShareSettingsTest") + self.assertEqual(data["data_dir"], "/tmp/OnionShareSettingsTest") self.assertFalse(data["close_after_first_download"]) self.assertEqual(data["connection_type"], "bundled") self.assertFalse(data["tor_bridges_use_obfs4"]) diff --git a/tests/TorGuiBaseTest.py b/tests/TorGuiBaseTest.py index 9a0bda3e..e437ac93 100644 --- a/tests/TorGuiBaseTest.py +++ b/tests/TorGuiBaseTest.py @@ -39,7 +39,7 @@ class TorGuiBaseTest(GuiBaseTest): # Get all of the settings in test_settings test_settings['connection_type'] = 'automatic' - test_settings['downloads_dir'] = '/tmp/OnionShare' + test_settings['data_dir'] = '/tmp/OnionShare' for key, val in common.settings.default_settings.items(): if key not in test_settings: test_settings[key] = val diff --git a/tests/test_onionshare_settings.py b/tests/test_onionshare_settings.py index d67621c4..f4be2930 100644 --- a/tests/test_onionshare_settings.py +++ b/tests/test_onionshare_settings.py @@ -64,7 +64,7 @@ class TestSettings: 'private_key': '', 'slug': '', 'hidservauth_string': '', - 'downloads_dir': os.path.expanduser('~/OnionShare'), + 'data_dir': os.path.expanduser('~/OnionShare'), 'public_mode': False } for key in settings_obj._settings: -- cgit v1.2.3-54-g00ecf From b1d5b29cf69371caf9936e4e4943bf6d9b909e07 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 12:15:27 -0800 Subject: Change version of Tor that supports v3 onion services to 0.3.5.7 --- onionshare/onion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index c747984e..4d9cbacf 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -411,7 +411,7 @@ class Onion(object): # Does this version of Tor support next-gen ('v3') onions? # Note, this is the version of Tor where this bug was fixed: # https://trac.torproject.org/projects/tor/ticket/28619 - self.supports_v3_onions = self.tor_version >= Version('0.4.0.0') + self.supports_v3_onions = self.tor_version >= Version('0.3.5.7') def is_authenticated(self): """ -- cgit v1.2.3-54-g00ecf From b75757ee49f58f77f524da9ec32cda8062697628 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 15:25:36 -0800 Subject: - Refactor the Web.ShareMode client_cancel variable to be Web.stop_q, a thread-safe queue that communicates to both share and receive mode when the user stops the server. In share mode this still stops sending the file. In receive mode, if there's a transfer in progress, it cancels it in the middle, and doesn't end up saving that file - In receive mode, make the receive mode dir right before saving a file (so if it doesn't complete, don't make an empty dir) - Minor UX tweak: resizing the window stretches the History widget first --- onionshare/web/receive_mode.py | 75 ++++++++++++++++++++-------- onionshare/web/share_mode.py | 10 +--- onionshare/web/web.py | 22 ++++++-- onionshare_gui/mode/__init__.py | 6 +++ onionshare_gui/mode/history.py | 9 +++- onionshare_gui/mode/receive_mode/__init__.py | 14 +++++- onionshare_gui/mode/share_mode/__init__.py | 2 +- onionshare_gui/onionshare_gui.py | 3 ++ 8 files changed, 102 insertions(+), 39 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 6985f38a..e1034af6 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -60,26 +60,11 @@ class ReceiveModeWeb(object): """ Upload files. """ - # Make sure the receive mode dir exists + # Figure out what the receive mode dir should be now = datetime.now() date_dir = now.strftime("%Y-%m-%d") time_dir = now.strftime("%H.%M.%S") receive_mode_dir = os.path.join(self.common.settings.get('downloads_dir'), date_dir, time_dir) - valid = True - try: - os.makedirs(receive_mode_dir, 0o700) - except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": receive_mode_dir - }) - print(strings._('error_cannot_create_downloads_dir').format(receive_mode_dir)) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) files = request.files.getlist('file[]') filenames = [] @@ -134,6 +119,23 @@ class ReceiveModeWeb(object): 'dir': receive_mode_dir }) + # Make sure receive mode dir exists before writing file + valid = True + try: + os.makedirs(receive_mode_dir, 0o700) + except PermissionError: + self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path, { + "receive_mode_dir": receive_mode_dir + }) + print(strings._('error_cannot_create_downloads_dir').format(receive_mode_dir)) + valid = False + if not valid: + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) print(strings._('receive_mode_received_file').format(local_path)) f.save(local_path) @@ -193,6 +195,7 @@ class ReceiveModeWSGIMiddleware(object): def __call__(self, environ, start_response): environ['web'] = self.web + environ['stop_q'] = self.web.stop_q return self.app(environ, start_response) @@ -201,7 +204,8 @@ class ReceiveModeTemporaryFile(object): A custom TemporaryFile that tells ReceiveModeRequest every time data gets written to it, in order to track the progress of uploads. """ - def __init__(self, filename, write_func, close_func): + def __init__(self, request, filename, write_func, close_func): + self.onionshare_request = request self.onionshare_filename = filename self.onionshare_write_func = write_func self.onionshare_close_func = close_func @@ -222,6 +226,11 @@ class ReceiveModeTemporaryFile(object): """ Custom write method that calls out to onionshare_write_func """ + if not self.onionshare_request.stop_q.empty(): + self.close() + self.onionshare_request.close() + return + bytes_written = self.f.write(b) self.onionshare_write_func(self.onionshare_filename, bytes_written) @@ -241,6 +250,12 @@ class ReceiveModeRequest(Request): def __init__(self, environ, populate_request=True, shallow=False): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) self.web = environ['web'] + self.stop_q = environ['stop_q'] + + self.web.common.log('ReceiveModeRequest', '__init__') + + # Prevent running the close() method more than once + self.closed = False # Is this a valid upload request? self.upload_request = False @@ -296,19 +311,35 @@ class ReceiveModeRequest(Request): 'complete': False } - return ReceiveModeTemporaryFile(filename, self.file_write_func, self.file_close_func) + return ReceiveModeTemporaryFile(self, filename, self.file_write_func, self.file_close_func) def close(self): """ Closing the request. """ super(ReceiveModeRequest, self).close() + + # Prevent calling this method more than once per request + if self.closed: + return + self.closed = True + + self.web.common.log('ReceiveModeRequest', 'close') + try: upload_id = self.upload_id - # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': upload_id - }) + + if not self.web.stop_q.empty(): + # Inform the GUI that the upload has canceled + self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { + 'id': upload_id + }) + else: + # Inform the GUI that the upload has finished + self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': upload_id + }) + self.web.receive_mode.uploads_in_progress.remove(upload_id) except AttributeError: pass diff --git a/onionshare/web/share_mode.py b/onionshare/web/share_mode.py index a57d0a39..eb487c42 100644 --- a/onionshare/web/share_mode.py +++ b/onionshare/web/share_mode.py @@ -34,11 +34,6 @@ class ShareModeWeb(object): # one download at a time. self.download_in_progress = False - # If the client closes the OnionShare window while a download is in progress, - # it should immediately stop serving the file. The client_cancel global is - # used to tell the download function that the client is canceling the download. - self.client_cancel = False - self.define_routes() def define_routes(self): @@ -146,9 +141,6 @@ class ShareModeWeb(object): basename = os.path.basename(self.download_filename) def generate(): - # The user hasn't canceled the download - self.client_cancel = False - # Starting a new download if not self.web.stay_open: self.download_in_progress = True @@ -160,7 +152,7 @@ class ShareModeWeb(object): canceled = False while not self.web.done: # The user has canceled the download, so stop serving the file - if self.client_cancel: + if not self.web.stop_q.empty(): self.web.add_request(self.web.REQUEST_CANCELED, path, { 'id': download_id }) diff --git a/onionshare/web/web.py b/onionshare/web/web.py index 0f156941..2ea5ff35 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -39,7 +39,8 @@ class Web(object): REQUEST_UPLOAD_FILE_RENAMED = 7 REQUEST_UPLOAD_SET_DIR = 8 REQUEST_UPLOAD_FINISHED = 9 - REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 10 + REQUEST_UPLOAD_CANCELED = 10 + REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE = 11 def __init__(self, common, is_gui, mode='share'): self.common = common @@ -58,6 +59,11 @@ class Web(object): # Are we running in GUI mode? self.is_gui = is_gui + # If the user stops the server while a transfer is in progress, it should + # immediately stop the transfer. In order to make it thread-safe, stop_q + # is a queue. If anything is in it, then the user stopped the server + self.stop_q = queue.Queue() + # Are we using receive mode? self.mode = mode if self.mode == 'receive': @@ -225,6 +231,13 @@ class Web(object): self.stay_open = stay_open + # Make sure the stop_q is empty when starting a new server + while not self.stop_q.empty(): + try: + self.stop_q.get(block=False) + except queue.Empty: + pass + # In Whonix, listen on 0.0.0.0 instead of 127.0.0.1 (#220) if os.path.exists('/usr/share/anon-ws-base-files/workstation'): host = '0.0.0.0' @@ -238,11 +251,10 @@ class Web(object): """ Stop the flask web server by loading /shutdown. """ + self.common.log('Web', 'stop', 'stopping server') - if self.mode == 'share': - # If the user cancels the download, let the download function know to stop - # serving the file - self.share_mode.client_cancel = True + # Let the mode know that the user stopped the server + self.stop_q.put(True) # To stop flask, load http://127.0.0.1://shutdown if self.running: diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 5110289f..bd363915 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -335,3 +335,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_FINISHED event. """ pass + + def handle_request_upload_canceled(self, event): + """ + Handle REQUEST_UPLOAD_CANCELED event. + """ + pass diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index e72a3838..5f895d75 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -184,7 +184,7 @@ class UploadHistoryItemFile(QtWidgets.QWidget): # macOS elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + subprocess.call(['open', '-R', abs_filename]) # Windows elif self.common.platform == 'Windows': @@ -295,6 +295,13 @@ class UploadHistoryItem(HistoryItem): ) self.label.setText(text) + elif data['action'] == 'canceled': + # Hide the progress bar + self.progress_bar.hide() + + # Change the label + self.label.setText(strings._('gui_canceled')) + class HistoryItemList(QtWidgets.QScrollArea): """ diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index c53f1ea1..cde2cb8a 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -83,7 +83,7 @@ class ReceiveMode(Mode): # Wrapper layout self.wrapper_layout = QtWidgets.QHBoxLayout() self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.history) + self.wrapper_layout.addWidget(self.history, stretch=1) self.setLayout(self.wrapper_layout) def get_stop_server_shutdown_timeout_text(self): @@ -198,6 +198,18 @@ class ReceiveMode(Mode): self.history.update_completed() self.history.update_in_progress() + def handle_request_upload_canceled(self, event): + """ + Handle REQUEST_UPLOAD_CANCELED event. + """ + self.history.update(event["data"]["id"], { + 'action': 'canceled' + }) + self.history.completed_count += 1 + self.history.in_progress_count -= 1 + self.history.update_completed() + self.history.update_in_progress() + def on_reload_settings(self): """ We should be ok to re-enable the 'Start Receive Mode' button now. diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 0cc00f92..d86a17e4 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -115,7 +115,7 @@ class ShareMode(Mode): # Wrapper layout self.wrapper_layout = QtWidgets.QHBoxLayout() self.wrapper_layout.addLayout(self.main_layout) - self.wrapper_layout.addWidget(self.history) + self.wrapper_layout.addWidget(self.history, stretch=1) self.setLayout(self.wrapper_layout) # Always start with focus on file selection diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py index eab3261e..c4f69b2d 100644 --- a/onionshare_gui/onionshare_gui.py +++ b/onionshare_gui/onionshare_gui.py @@ -396,6 +396,9 @@ class OnionShareGui(QtWidgets.QMainWindow): elif event["type"] == Web.REQUEST_UPLOAD_FINISHED: mode.handle_request_upload_finished(event) + elif event["type"] == Web.REQUEST_UPLOAD_CANCELED: + mode.handle_request_upload_canceled(event) + if event["type"] == Web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE: Alert(self.common, strings._('error_cannot_create_downloads_dir').format(event["data"]["receive_mode_dir"])) -- cgit v1.2.3-54-g00ecf From 1a0dbc9d19dd0287c2e896bcace0341ec9aa1a25 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 15:32:09 -0800 Subject: Remove un-used images --- share/images/receive_icon.png | Bin 2120 -> 0 bytes share/images/share_icon.png | Bin 2076 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 share/images/receive_icon.png delete mode 100644 share/images/share_icon.png diff --git a/share/images/receive_icon.png b/share/images/receive_icon.png deleted file mode 100644 index ad879b6e..00000000 Binary files a/share/images/receive_icon.png and /dev/null differ diff --git a/share/images/share_icon.png b/share/images/share_icon.png deleted file mode 100644 index cd9bd98e..00000000 Binary files a/share/images/share_icon.png and /dev/null differ -- cgit v1.2.3-54-g00ecf From 4aa8a1d4c619751a4c5da7fb029fa85cfcf0b297 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 15:33:33 -0800 Subject: In SettingsDialog, rename downloads_layout to data_dir_layout --- onionshare_gui/settings_dialog.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index aa94b551..1fe6dc53 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -193,14 +193,14 @@ class SettingsDialog(QtWidgets.QDialog): self.data_dir_lineedit.setReadOnly(True) data_dir_button = QtWidgets.QPushButton(strings._('gui_settings_data_dir_browse_button')) data_dir_button.clicked.connect(self.data_dir_button_clicked) - downloads_layout = QtWidgets.QHBoxLayout() - downloads_layout.addWidget(data_dir_label) - downloads_layout.addWidget(self.data_dir_lineedit) - downloads_layout.addWidget(data_dir_button) + data_dir_layout = QtWidgets.QHBoxLayout() + data_dir_layout.addWidget(data_dir_label) + data_dir_layout.addWidget(self.data_dir_lineedit) + data_dir_layout.addWidget(data_dir_button) # Receiving options layout receiving_group_layout = QtWidgets.QVBoxLayout() - receiving_group_layout.addLayout(downloads_layout) + receiving_group_layout.addLayout(data_dir_layout) receiving_group = QtWidgets.QGroupBox(strings._("gui_settings_receiving_label")) receiving_group.setLayout(receiving_group_layout) -- cgit v1.2.3-54-g00ecf From 604f551f335af5f583969e503f82aaa6a72c9885 Mon Sep 17 00:00:00 2001 From: Taro Tanaka Date: Sun, 20 Jan 2019 05:19:01 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 148 +++++++++++++++++++++++++-------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index e846a5f0..77ae9b58 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -72,7 +72,7 @@ "gui_settings_window_title": "設定", "gui_settings_whats_this": "これは何ですか?", "gui_settings_stealth_option": "クライアント認証を使用", - "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。", + "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、\nクリックしてHidServAuthをコピーできます。", "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする", "gui_settings_autoupdate_option": "更新通知を起動します", "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}", @@ -112,77 +112,77 @@ "settings_error_unknown": "設定を解釈できないため、Torコントローラーと接続できません。", "settings_error_automatic": "Torコントローラーと接続できません。Torブラウザ(torproject.orgから入手できる)がバックグラウンドで動作していますか?", "settings_error_socket_port": "{}:{}でTorコントローラーと接続できません。", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_connect_to_tor_for_onion_settings": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "settings_error_socket_file": "ソケットファイル{}を使用してTorコントローラーと接続できません。", + "settings_error_auth": "{}:{}と接続できましたが、認証ができません。これは実際にTorコントローラーですか?", + "settings_error_missing_password": "Torコントローラーと接続できましたが、認証にはパスワードが必要です。", + "settings_error_unreadable_cookie_file": "Torコントローラーと接続できましたが、パスワードが診違っているあるいはクッキーファイルの読み出し許可がないかもしれない。", + "settings_error_bundled_tor_not_supported": "OnionShareに組み込まれているTorバージョンはWindowsやmacOSの開発者モードで動作できません。", + "settings_error_bundled_tor_timeout": "Torとの接続は時間がかかり過ぎます。インターネットとの接続、あるいはシステム・クロックの精度には問題がありますか?", + "settings_error_bundled_tor_broken": "OnionShareはバックグラウンドで動作しているTorと接続できませんでした:\n{}", + "settings_test_success": "Torコントローラーと接続完了。\n\nTorバージョン:{}\nエフェメラルonionサービスをサポートする:{}\nクライアント認証をサポートする:{}\nnext-gen .onionアドレスをサポートする:{}.", + "error_tor_protocol_error": "Torとのエラーが生じました: {}", + "error_tor_protocol_error_unknown": "Torとの未知のエラーが生じました", + "error_invalid_private_key": "この秘密鍵形式は未対応である", + "connecting_to_tor": "Torネットワークと接続中", + "update_available": "OnionShareの新バージョンはリリースされました。こちらから入手できます。

    現行バージョンは{}そして最新バージョンは{}。", + "update_error_check_error": "新バージョンのチェックをできなかった:OnionShare公式サイトによれば、最新バージョンは認識できない '{}'です…", + "update_error_invalid_latest_version": "新バージョンのチェックをできなかった:多分Torと接続していない、あるいはOnionShare公式サイトはダウンかもしれない?", + "update_not_available": "OnionShareの最新バージョンを使っています。", + "gui_tor_connection_ask": "設定を開いて、Torとの接続問題を解決しますか?", + "gui_tor_connection_ask_open_settings": "はい", + "gui_tor_connection_ask_quit": "終了", + "gui_tor_connection_error_settings": "設定でTorとの接続方法を変更してみて下さい。", + "gui_tor_connection_canceled": "Torと接続できませんでした。\n\nインターネット接続を確認してから、OnionShareを再開してTorとの接続を設定して下さい。", + "gui_tor_connection_lost": "Torから切断されました。", + "gui_server_started_after_timeout": "サーバーが起動した前、自動停止タイマーがタイムアウトしました。\n再びファイル共有をして下さい。", + "gui_server_timeout_expired": "自動停止タイマーはすでにタイムアウトしています。\n共有し始めるにはリセットして下さい。", + "share_via_onionshare": "OnionShareで共有する", + "gui_connect_to_tor_for_onion_settings": "onionサービス設定を見るのにTorと接続して下さい", + "gui_use_legacy_v2_onions_checkbox": "レガシーアドレスを使用する", + "gui_save_private_key_checkbox": "永続的アドレスを使用する", + "gui_share_url_description": "このOnionShareアドレスを持つ限り誰でもTorブラウザーを利用してこのファイルをダウンロードできます", + "gui_receive_url_description": "このOnionShareアドレスを持つ限り誰でもTorブラウザーを利用してこのPCにファイルをアップロードできます", + "gui_url_label_persistent": "このファイル共有には自動停止はありません。

    その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)", + "gui_url_label_stay_open": "このファイル共有には自動停止はありません。", + "gui_url_label_onetime": "このファイル共有は最初の完了後に停止されます。", + "gui_url_label_onetime_and_persistent": "このファイル共有には自動停止はありません。

    その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)", + "gui_status_indicator_share_stopped": "共有の準備完了", + "gui_status_indicator_share_working": "起動しています…", + "gui_status_indicator_share_started": "共有中", + "gui_status_indicator_receive_stopped": "受信の準備完了", + "gui_status_indicator_receive_working": "起動しています…", + "gui_status_indicator_receive_started": "受信中", + "gui_file_info": "{} ファイル, {}", + "gui_file_info_single": "{} ファイル, {}", + "history_in_progress_tooltip": "{} 進行中", + "history_completed_tooltip": "{} 完了", + "info_in_progress_uploads_tooltip": "{} 進行中のアップロード", + "info_completed_uploads_tooltip": "{} 完了のアップロード", + "error_cannot_create_downloads_dir": "受信モードフォルダを作成できなかった: {}", + "receive_mode_downloads_dir": "受信されるファイルはこのフォルダに保存されます: {}", + "receive_mode_warning": "警告:受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。", + "gui_receive_mode_warning": "受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。

    悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。", + "receive_mode_upload_starting": "ファイルサイズ{}のアップロードが実行中", + "receive_mode_received_file": "受信した: {}", + "gui_mode_share_button": "ファイル共有", + "gui_mode_receive_button": "ファイル受信", + "gui_settings_receiving_label": "受信設定", + "gui_settings_downloads_label": "保存フォルダ", + "gui_settings_downloads_button": "選ぶ", + "gui_settings_public_mode_checkbox": "公開モード", + "systray_close_server_title": "OnionShareサーバーは閉鎖されました", + "systray_close_server_message": "ユーザーがサーバーを閉鎖しました", + "systray_page_loaded_title": "OnionShareページはロードされました", + "systray_download_page_loaded_message": "ユーザーがダウンロードページをロードしました", + "systray_upload_page_loaded_message": "ユーザーがアップロードページをロードしました", + "gui_uploads": "アップロード履歴", + "gui_no_uploads": "アップロードはまだありません", + "gui_clear_history": "全てをクリアする", + "gui_upload_in_progress": "アップロード開始しました {}", + "gui_upload_finished_range": "{}を{}にアップロードしました", + "gui_upload_finished": "{}をアップロードしました", + "gui_download_in_progress": "ダウンロード開始しました {}", + "gui_open_folder_error_nautilus": "nautilusを利用できないためフォルダーを開けません。ファイルはここに保存されました: {}", + "gui_settings_language_label": "優先言語", + "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。" } -- cgit v1.2.3-54-g00ecf From d36ac4bb59a41952bb6bc525607009f0270cc48d Mon Sep 17 00:00:00 2001 From: R Date: Sun, 20 Jan 2019 18:21:47 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 131 ++++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index 21ebbec5..bca7cc99 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -71,23 +71,23 @@ "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", - "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن", "gui_settings_autoupdate_timestamp": "", "gui_settings_autoupdate_timestamp_never": "هرگز", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "بررسی برای نسخه جدید", "gui_settings_general_label": "تنظیمات کلی", "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", - "gui_settings_close_after_first_download_option": "", + "gui_settings_close_after_first_download_option": "توقف اشتراک پس از اولین دانلود", "gui_settings_connection_type_label": "", "gui_settings_connection_type_bundled_option": "", "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", + "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل", + "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت", + "gui_settings_connection_type_test_button": "تست اتصال به Tor", + "gui_settings_control_port_label": "پورت کنترل", + "gui_settings_socket_file_label": "فایل سوکت‌", "gui_settings_socks_label": "پورت SOCKS", - "gui_settings_authenticate_label": "", + "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor", "gui_settings_authenticate_no_auth_option": "", "gui_settings_authenticate_password_option": "رمز عبور", "gui_settings_password_label": "رمز عبور", @@ -98,14 +98,14 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم", + "gui_settings_tor_bridges_custom_label": "میتوانید از https://bridges.torproject.org بریج دریافت کنید", + "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.", "gui_settings_button_save": "ذخیره", "gui_settings_button_cancel": "لغو", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", + "gui_settings_button_help": "راهنما", + "gui_settings_shutdown_timeout_checkbox": "استفاده از تایمر توقف خودکار", + "gui_settings_shutdown_timeout": "توقف اشتراک در:", "settings_error_unknown": "", "settings_error_automatic": "", "settings_error_socket_port": "", @@ -119,70 +119,71 @@ "settings_test_success": "", "error_tor_protocol_error": "", "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", + "error_invalid_private_key": "این نوع کلید خصوصی پشتیبانی نمی شود", + "connecting_to_tor": "در حال اتصال به شبکه Tor", "update_available": "", "update_error_check_error": "", "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.", + "gui_tor_connection_ask": "باز کردن تنظیمات برای ساماندهی اتصال به Tor؟", "gui_tor_connection_ask_open_settings": "بله", "gui_tor_connection_ask_quit": "خروج", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", + "gui_tor_connection_error_settings": "تغییر نحوه اتصال OnionShare به شبکه Tor در تنظیمات.", + "gui_tor_connection_canceled": "اتصال به Tor برقرار نشد.\n\nمطمئن شوید که به اینترنت متصل هستید، سپس OnionShare را دوباره باز کرده و اتصال آن را به Tor دوباره برقرار کنید.", + "gui_tor_connection_lost": "اتصال با Tor قطع شده است.", + "gui_server_started_after_timeout": "تایمر توقف خودکار قبل از آغاز سرور به پایان رسید.\nلطفا یک اشتراک جدید درست کنید.", + "gui_server_timeout_expired": "تایمر توقف خودکار به پایان رسید.\nلطفا برای آغاز اشتراک گذاری آن را به روز رسانی کنید.", + "share_via_onionshare": "OnionShare کنید", + "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده", + "gui_save_private_key_checkbox": "استفاده از یک آدرس پایا", "gui_share_url_description": "", "gui_receive_url_description": "", "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", + "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.", + "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", + "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری", + "gui_status_indicator_share_working": "در حال شروع…", + "gui_status_indicator_share_started": "در حال اشتراک گذاری", + "gui_status_indicator_receive_stopped": "آماده دریافت", + "gui_status_indicator_receive_working": "در حال شروع…", "gui_status_indicator_receive_started": "درحال دریافت", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", + "gui_file_info": "{} فایل ها، {}", + "gui_file_info_single": "{} فایل، {}", + "history_in_progress_tooltip": "{} در حال انجام", + "history_completed_tooltip": "{} کامل شد", + "info_in_progress_uploads_tooltip": "{} آپلود در حال انجام", + "info_completed_uploads_tooltip": "{} آپلود کامل شد", + "error_cannot_create_downloads_dir": "ناتوانی در ایجاد پوشه حالت دریافت: {}", + "receive_mode_downloads_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}", + "receive_mode_warning": "هشدار: حالت دریافت به سایر افراد اجازه می دهد تا به روی کامپیوتر شما فایل آپلود کنند. برخی فایل ها را اگر باز کنید پتانسیل آن را دارند تا کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی که از کسانی دریافت کردید که به آن ها اعتماد دارید را باز کنید، یا اگر میدانید دارید چه کار میکنید.", + "gui_receive_mode_warning": "حالت دریافت به سایر افراد اجازه می دهد تا روی کامپیوتر شما فایل آپلود کنند.

    برخی فایل ها را اگر باز کنید پتانسیل این را دارند که کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی را باز کنید که از کسانی دریافت کرده اید که به آن ها اعتماد دارید، یا میدانید دارید چه کار میکنید.", + "receive_mode_upload_starting": "آپلود حجم کلی {} در حال آغاز می باشد", + "receive_mode_received_file": "دریافت شده: {}", + "gui_mode_share_button": "اشتراک گذاری فایل ها", + "gui_mode_receive_button": "دریافت فایل ها", + "gui_settings_receiving_label": "تنظیمات دریافت", + "gui_settings_downloads_label": "ذخیره فایل ها در", "gui_settings_downloads_button": "فهرست", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", + "gui_settings_public_mode_checkbox": "حالت عمومی", + "systray_close_server_title": "سرور OnionShare بسته شد", + "systray_close_server_message": "یک کاربر سرور را بست", + "systray_page_loaded_title": "صفحه OnionShare بارگذاری شد", + "systray_download_page_loaded_message": "یک کاربر صفحه دانلود را بارگذاری کرد", + "systray_upload_page_loaded_message": "یک کاربر صفحه آپلود را بارگذاری کرد", + "gui_uploads": "تاریخچه آپلود", + "gui_no_uploads": "هیچ آپلودی هنوز وجود ندارد", + "gui_clear_history": "پاکسازی همه", + "gui_upload_in_progress": "آپلود آغاز شد {}", + "gui_upload_finished_range": "{} به {} آپلود شد", + "gui_upload_finished": "{} آپلود شد", "gui_download_in_progress": "دانلود آغاز شد {}", - "gui_open_folder_error_nautilus": "", + "gui_open_folder_error_nautilus": "ناتوانی در باز کردن پوشه به دلیل موجود نبودن ناتیلوس. فایل در اینجا قرار دارد: {}", "gui_settings_language_label": "زبان ترجیحی", - "gui_settings_language_changed_notice": "", + "gui_settings_language_changed_notice": "ری استارت OnionShare برای دیدن نتیجه اعمال تغییر در زبان.", "timeout_upload_still_running": "انتظار برای تکمیل آپلود", "gui_add_files": "افزودن فایل ها", - "gui_add_folder": "افزودن پوشه" + "gui_add_folder": "افزودن پوشه", + "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion" } -- cgit v1.2.3-54-g00ecf From 634b8ecebd72c1108ad99bcbe3c096cf6cd14a94 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 15:49:08 -0800 Subject: When canceling a receive mode transfer, display date range in the UI --- onionshare_gui/mode/history.py | 21 +++++++++++++++++---- share/locale/en.json | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 02c34ec4..0ebd8b93 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -45,19 +45,32 @@ class HistoryItem(QtWidgets.QWidget): When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ + return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started) + + def get_canceled_label_text(self, started): + """ + When an item is canceled, returns a string displaying the start/end datetime range. + started is a datetime object. + """ + return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started) + + def _get_label_text(self, string_name, string_range_name, started): + """ + Return a string that contains a date, or date range. + """ ended = datetime.now() if started.year == ended.year and started.month == ended.month and started.day == ended.day: if started.hour == ended.hour and started.minute == ended.minute: - text = strings._('gui_all_modes_transfer_finished').format( + text = strings._(string_name).format( started.strftime("%b %d, %I:%M%p") ) else: - text = strings._('gui_all_modes_transfer_finished_range').format( + text = strings._(string_range_name).format( started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p") ) else: - text = strings._('gui_all_modes_transfer_finished_range').format( + text = strings._(string_range_name).format( started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p") ) @@ -311,7 +324,7 @@ class ReceiveHistoryItem(HistoryItem): self.progress_bar.hide() # Change the label - self.label.setText(strings._('gui_canceled')) + self.label.setText(self.get_canceled_label_text(self.started)) class HistoryItemList(QtWidgets.QScrollArea): diff --git a/share/locale/en.json b/share/locale/en.json index a3307e49..3ad2efda 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -171,6 +171,8 @@ "gui_all_modes_transfer_started": "Started {}", "gui_all_modes_transfer_finished_range": "Transferred {} - {}", "gui_all_modes_transfer_finished": "Transferred {}", + "gui_all_modes_transfer_canceled_range": "Canceled {} - {}", + "gui_all_modes_transfer_canceled": "Canceled {}", "gui_all_modes_progress_complete": "%p%, {0:s} elapsed.", "gui_all_modes_progress_starting": "{0:s}, %p% (calculating)", "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", -- cgit v1.2.3-54-g00ecf From b6928a6d0edec347f5c39a90078b3deee5102095 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 16:00:18 -0800 Subject: Oops, I missed this when resolving merge conflicts --- onionshare/web/receive_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 82f0de42..a64c2a73 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -124,10 +124,10 @@ class ReceiveModeWeb(object): try: os.makedirs(receive_mode_dir, 0o700) except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DOWNLOADS_DIR_CANNOT_CREATE, request.path, { + self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { "receive_mode_dir": receive_mode_dir }) - print(strings._('error_cannot_create_downloads_dir').format(receive_mode_dir)) + print(strings._('error_cannot_create_data_dir').format(receive_mode_dir)) valid = False if not valid: flash('Error uploading, please inform the OnionShare user', 'error') -- cgit v1.2.3-54-g00ecf From a1f8f0bd2bfbe8adf63fafe0be5e176e1d8f17fd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 16:06:19 -0800 Subject: Weblate (#875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Added translation using Weblate (Hebrew) * Added translation using Weblate (Japanese) * Added translation using Weblate (Shona) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 131 +++++++++++++++++++++---------------------- share/locale/ja.json | 154 +++++++++++++++++++++++++-------------------------- 2 files changed, 143 insertions(+), 142 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index 21ebbec5..bca7cc99 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -71,23 +71,23 @@ "gui_settings_stealth_option": "", "gui_settings_stealth_hidservauth_string": "", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", - "gui_settings_autoupdate_option": "", + "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن", "gui_settings_autoupdate_timestamp": "", "gui_settings_autoupdate_timestamp_never": "هرگز", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "بررسی برای نسخه جدید", "gui_settings_general_label": "تنظیمات کلی", "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", - "gui_settings_close_after_first_download_option": "", + "gui_settings_close_after_first_download_option": "توقف اشتراک پس از اولین دانلود", "gui_settings_connection_type_label": "", "gui_settings_connection_type_bundled_option": "", "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", - "gui_settings_control_port_label": "", - "gui_settings_socket_file_label": "", + "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل", + "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت", + "gui_settings_connection_type_test_button": "تست اتصال به Tor", + "gui_settings_control_port_label": "پورت کنترل", + "gui_settings_socket_file_label": "فایل سوکت‌", "gui_settings_socks_label": "پورت SOCKS", - "gui_settings_authenticate_label": "", + "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor", "gui_settings_authenticate_no_auth_option": "", "gui_settings_authenticate_password_option": "رمز عبور", "gui_settings_password_label": "رمز عبور", @@ -98,14 +98,14 @@ "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم", + "gui_settings_tor_bridges_custom_label": "میتوانید از https://bridges.torproject.org بریج دریافت کنید", + "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.", "gui_settings_button_save": "ذخیره", "gui_settings_button_cancel": "لغو", - "gui_settings_button_help": "", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", + "gui_settings_button_help": "راهنما", + "gui_settings_shutdown_timeout_checkbox": "استفاده از تایمر توقف خودکار", + "gui_settings_shutdown_timeout": "توقف اشتراک در:", "settings_error_unknown": "", "settings_error_automatic": "", "settings_error_socket_port": "", @@ -119,70 +119,71 @@ "settings_test_success": "", "error_tor_protocol_error": "", "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", + "error_invalid_private_key": "این نوع کلید خصوصی پشتیبانی نمی شود", + "connecting_to_tor": "در حال اتصال به شبکه Tor", "update_available": "", "update_error_check_error": "", "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.", + "gui_tor_connection_ask": "باز کردن تنظیمات برای ساماندهی اتصال به Tor؟", "gui_tor_connection_ask_open_settings": "بله", "gui_tor_connection_ask_quit": "خروج", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", + "gui_tor_connection_error_settings": "تغییر نحوه اتصال OnionShare به شبکه Tor در تنظیمات.", + "gui_tor_connection_canceled": "اتصال به Tor برقرار نشد.\n\nمطمئن شوید که به اینترنت متصل هستید، سپس OnionShare را دوباره باز کرده و اتصال آن را به Tor دوباره برقرار کنید.", + "gui_tor_connection_lost": "اتصال با Tor قطع شده است.", + "gui_server_started_after_timeout": "تایمر توقف خودکار قبل از آغاز سرور به پایان رسید.\nلطفا یک اشتراک جدید درست کنید.", + "gui_server_timeout_expired": "تایمر توقف خودکار به پایان رسید.\nلطفا برای آغاز اشتراک گذاری آن را به روز رسانی کنید.", + "share_via_onionshare": "OnionShare کنید", + "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده", + "gui_save_private_key_checkbox": "استفاده از یک آدرس پایا", "gui_share_url_description": "", "gui_receive_url_description": "", "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", + "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.", + "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", + "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری", + "gui_status_indicator_share_working": "در حال شروع…", + "gui_status_indicator_share_started": "در حال اشتراک گذاری", + "gui_status_indicator_receive_stopped": "آماده دریافت", + "gui_status_indicator_receive_working": "در حال شروع…", "gui_status_indicator_receive_started": "درحال دریافت", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", + "gui_file_info": "{} فایل ها، {}", + "gui_file_info_single": "{} فایل، {}", + "history_in_progress_tooltip": "{} در حال انجام", + "history_completed_tooltip": "{} کامل شد", + "info_in_progress_uploads_tooltip": "{} آپلود در حال انجام", + "info_completed_uploads_tooltip": "{} آپلود کامل شد", + "error_cannot_create_downloads_dir": "ناتوانی در ایجاد پوشه حالت دریافت: {}", + "receive_mode_downloads_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}", + "receive_mode_warning": "هشدار: حالت دریافت به سایر افراد اجازه می دهد تا به روی کامپیوتر شما فایل آپلود کنند. برخی فایل ها را اگر باز کنید پتانسیل آن را دارند تا کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی که از کسانی دریافت کردید که به آن ها اعتماد دارید را باز کنید، یا اگر میدانید دارید چه کار میکنید.", + "gui_receive_mode_warning": "حالت دریافت به سایر افراد اجازه می دهد تا روی کامپیوتر شما فایل آپلود کنند.

    برخی فایل ها را اگر باز کنید پتانسیل این را دارند که کنترل کامپیوتر شما را در دست بگیرند. فقط چیزهایی را باز کنید که از کسانی دریافت کرده اید که به آن ها اعتماد دارید، یا میدانید دارید چه کار میکنید.", + "receive_mode_upload_starting": "آپلود حجم کلی {} در حال آغاز می باشد", + "receive_mode_received_file": "دریافت شده: {}", + "gui_mode_share_button": "اشتراک گذاری فایل ها", + "gui_mode_receive_button": "دریافت فایل ها", + "gui_settings_receiving_label": "تنظیمات دریافت", + "gui_settings_downloads_label": "ذخیره فایل ها در", "gui_settings_downloads_button": "فهرست", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", + "gui_settings_public_mode_checkbox": "حالت عمومی", + "systray_close_server_title": "سرور OnionShare بسته شد", + "systray_close_server_message": "یک کاربر سرور را بست", + "systray_page_loaded_title": "صفحه OnionShare بارگذاری شد", + "systray_download_page_loaded_message": "یک کاربر صفحه دانلود را بارگذاری کرد", + "systray_upload_page_loaded_message": "یک کاربر صفحه آپلود را بارگذاری کرد", + "gui_uploads": "تاریخچه آپلود", + "gui_no_uploads": "هیچ آپلودی هنوز وجود ندارد", + "gui_clear_history": "پاکسازی همه", + "gui_upload_in_progress": "آپلود آغاز شد {}", + "gui_upload_finished_range": "{} به {} آپلود شد", + "gui_upload_finished": "{} آپلود شد", "gui_download_in_progress": "دانلود آغاز شد {}", - "gui_open_folder_error_nautilus": "", + "gui_open_folder_error_nautilus": "ناتوانی در باز کردن پوشه به دلیل موجود نبودن ناتیلوس. فایل در اینجا قرار دارد: {}", "gui_settings_language_label": "زبان ترجیحی", - "gui_settings_language_changed_notice": "", + "gui_settings_language_changed_notice": "ری استارت OnionShare برای دیدن نتیجه اعمال تغییر در زبان.", "timeout_upload_still_running": "انتظار برای تکمیل آپلود", "gui_add_files": "افزودن فایل ها", - "gui_add_folder": "افزودن پوشه" + "gui_add_folder": "افزودن پوشه", + "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion" } diff --git a/share/locale/ja.json b/share/locale/ja.json index acddc8c2..77ae9b58 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -72,7 +72,7 @@ "gui_settings_window_title": "設定", "gui_settings_whats_this": "これは何ですか?", "gui_settings_stealth_option": "クライアント認証を使用", - "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。", + "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、\nクリックしてHidServAuthをコピーできます。", "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする", "gui_settings_autoupdate_option": "更新通知を起動します", "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}", @@ -109,80 +109,80 @@ "gui_settings_button_help": "ヘルプ", "gui_settings_shutdown_timeout_checkbox": "自動停止タイマーを使用する", "gui_settings_shutdown_timeout": "共有を停止する時間:", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_connect_to_tor_for_onion_settings": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", - "info_in_progress_uploads_tooltip": "", - "info_completed_uploads_tooltip": "", - "error_cannot_create_downloads_dir": "", - "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", - "gui_settings_downloads_label": "", - "gui_settings_downloads_button": "", - "gui_settings_public_mode_checkbox": "", - "systray_close_server_title": "", - "systray_close_server_message": "", - "systray_page_loaded_title": "", - "systray_download_page_loaded_message": "", - "systray_upload_page_loaded_message": "", - "gui_uploads": "", - "gui_no_uploads": "", - "gui_clear_history": "", - "gui_upload_in_progress": "", - "gui_upload_finished_range": "", - "gui_upload_finished": "", - "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "settings_error_unknown": "設定を解釈できないため、Torコントローラーと接続できません。", + "settings_error_automatic": "Torコントローラーと接続できません。Torブラウザ(torproject.orgから入手できる)がバックグラウンドで動作していますか?", + "settings_error_socket_port": "{}:{}でTorコントローラーと接続できません。", + "settings_error_socket_file": "ソケットファイル{}を使用してTorコントローラーと接続できません。", + "settings_error_auth": "{}:{}と接続できましたが、認証ができません。これは実際にTorコントローラーですか?", + "settings_error_missing_password": "Torコントローラーと接続できましたが、認証にはパスワードが必要です。", + "settings_error_unreadable_cookie_file": "Torコントローラーと接続できましたが、パスワードが診違っているあるいはクッキーファイルの読み出し許可がないかもしれない。", + "settings_error_bundled_tor_not_supported": "OnionShareに組み込まれているTorバージョンはWindowsやmacOSの開発者モードで動作できません。", + "settings_error_bundled_tor_timeout": "Torとの接続は時間がかかり過ぎます。インターネットとの接続、あるいはシステム・クロックの精度には問題がありますか?", + "settings_error_bundled_tor_broken": "OnionShareはバックグラウンドで動作しているTorと接続できませんでした:\n{}", + "settings_test_success": "Torコントローラーと接続完了。\n\nTorバージョン:{}\nエフェメラルonionサービスをサポートする:{}\nクライアント認証をサポートする:{}\nnext-gen .onionアドレスをサポートする:{}.", + "error_tor_protocol_error": "Torとのエラーが生じました: {}", + "error_tor_protocol_error_unknown": "Torとの未知のエラーが生じました", + "error_invalid_private_key": "この秘密鍵形式は未対応である", + "connecting_to_tor": "Torネットワークと接続中", + "update_available": "OnionShareの新バージョンはリリースされました。こちらから入手できます。

    現行バージョンは{}そして最新バージョンは{}。", + "update_error_check_error": "新バージョンのチェックをできなかった:OnionShare公式サイトによれば、最新バージョンは認識できない '{}'です…", + "update_error_invalid_latest_version": "新バージョンのチェックをできなかった:多分Torと接続していない、あるいはOnionShare公式サイトはダウンかもしれない?", + "update_not_available": "OnionShareの最新バージョンを使っています。", + "gui_tor_connection_ask": "設定を開いて、Torとの接続問題を解決しますか?", + "gui_tor_connection_ask_open_settings": "はい", + "gui_tor_connection_ask_quit": "終了", + "gui_tor_connection_error_settings": "設定でTorとの接続方法を変更してみて下さい。", + "gui_tor_connection_canceled": "Torと接続できませんでした。\n\nインターネット接続を確認してから、OnionShareを再開してTorとの接続を設定して下さい。", + "gui_tor_connection_lost": "Torから切断されました。", + "gui_server_started_after_timeout": "サーバーが起動した前、自動停止タイマーがタイムアウトしました。\n再びファイル共有をして下さい。", + "gui_server_timeout_expired": "自動停止タイマーはすでにタイムアウトしています。\n共有し始めるにはリセットして下さい。", + "share_via_onionshare": "OnionShareで共有する", + "gui_connect_to_tor_for_onion_settings": "onionサービス設定を見るのにTorと接続して下さい", + "gui_use_legacy_v2_onions_checkbox": "レガシーアドレスを使用する", + "gui_save_private_key_checkbox": "永続的アドレスを使用する", + "gui_share_url_description": "このOnionShareアドレスを持つ限り誰でもTorブラウザーを利用してこのファイルをダウンロードできます", + "gui_receive_url_description": "このOnionShareアドレスを持つ限り誰でもTorブラウザーを利用してこのPCにファイルをアップロードできます", + "gui_url_label_persistent": "このファイル共有には自動停止はありません。

    その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)", + "gui_url_label_stay_open": "このファイル共有には自動停止はありません。", + "gui_url_label_onetime": "このファイル共有は最初の完了後に停止されます。", + "gui_url_label_onetime_and_persistent": "このファイル共有には自動停止はありません。

    その次の共有は同じアドレスを再利用します。(1回限りのアドレスには、設定で「永続的アドレス」を無効にして下さい。)", + "gui_status_indicator_share_stopped": "共有の準備完了", + "gui_status_indicator_share_working": "起動しています…", + "gui_status_indicator_share_started": "共有中", + "gui_status_indicator_receive_stopped": "受信の準備完了", + "gui_status_indicator_receive_working": "起動しています…", + "gui_status_indicator_receive_started": "受信中", + "gui_file_info": "{} ファイル, {}", + "gui_file_info_single": "{} ファイル, {}", + "history_in_progress_tooltip": "{} 進行中", + "history_completed_tooltip": "{} 完了", + "info_in_progress_uploads_tooltip": "{} 進行中のアップロード", + "info_completed_uploads_tooltip": "{} 完了のアップロード", + "error_cannot_create_downloads_dir": "受信モードフォルダを作成できなかった: {}", + "receive_mode_downloads_dir": "受信されるファイルはこのフォルダに保存されます: {}", + "receive_mode_warning": "警告:受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。", + "gui_receive_mode_warning": "受信モードで他の人はあなたのPCへファイルをアップロードできるようにします。

    悪意なファイルを開いたら、PCは感染される可能性があります。ファイル内容を完全に理解しない場合、信用している人のみからのファイルを開いて下さい。", + "receive_mode_upload_starting": "ファイルサイズ{}のアップロードが実行中", + "receive_mode_received_file": "受信した: {}", + "gui_mode_share_button": "ファイル共有", + "gui_mode_receive_button": "ファイル受信", + "gui_settings_receiving_label": "受信設定", + "gui_settings_downloads_label": "保存フォルダ", + "gui_settings_downloads_button": "選ぶ", + "gui_settings_public_mode_checkbox": "公開モード", + "systray_close_server_title": "OnionShareサーバーは閉鎖されました", + "systray_close_server_message": "ユーザーがサーバーを閉鎖しました", + "systray_page_loaded_title": "OnionShareページはロードされました", + "systray_download_page_loaded_message": "ユーザーがダウンロードページをロードしました", + "systray_upload_page_loaded_message": "ユーザーがアップロードページをロードしました", + "gui_uploads": "アップロード履歴", + "gui_no_uploads": "アップロードはまだありません", + "gui_clear_history": "全てをクリアする", + "gui_upload_in_progress": "アップロード開始しました {}", + "gui_upload_finished_range": "{}を{}にアップロードしました", + "gui_upload_finished": "{}をアップロードしました", + "gui_download_in_progress": "ダウンロード開始しました {}", + "gui_open_folder_error_nautilus": "nautilusを利用できないためフォルダーを開けません。ファイルはここに保存されました: {}", + "gui_settings_language_label": "優先言語", + "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。" } -- cgit v1.2.3-54-g00ecf From 04e43856673abcb4ec9830022f5b3b800b5b44d8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 16:20:36 -0800 Subject: In receive mode, only tell the GUI that a new request is coming in if it contains files --- onionshare/web/receive_mode.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 2b4e8faf..c319ce13 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -275,11 +275,8 @@ class ReceiveModeRequest(Request): strings._("receive_mode_upload_starting").format(self.web.common.human_readable_filesize(self.content_length)) )) - # Tell the GUI - self.web.add_request(self.web.REQUEST_STARTED, self.path, { - 'id': self.upload_id, - 'content_length': self.content_length - }) + # Don't tell the GUI that a request has started until we start receiving files + self.told_gui_about_request = False self.web.receive_mode.uploads_in_progress.append(self.upload_id) @@ -291,6 +288,14 @@ class ReceiveModeRequest(Request): writable stream. """ if self.upload_request: + if not self.told_gui_about_request: + # Tell the GUI about the request + self.web.add_request(self.web.REQUEST_STARTED, self.path, { + 'id': self.upload_id, + 'content_length': self.content_length + }) + self.told_gui_about_request = True + self.progress[filename] = { 'uploaded_bytes': 0, 'complete': False -- cgit v1.2.3-54-g00ecf From 02538520a20fca491add9cb11b8dd3978ffe4331 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 Jan 2019 16:34:56 -0800 Subject: Skip updating or canceling items that haven't been added --- onionshare_gui/mode/history.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index bb419ec7..b54b6f5f 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -355,13 +355,15 @@ class HistoryItemList(QtWidgets.QScrollArea): """ Update an item. Override this method. """ - self.items[id].update(data) + if id in self.items: + self.items[id].update(data) def cancel(self, id): """ Cancel an item. Override this method. """ - self.items[id].cancel() + if id in self.items: + self.items[id].cancel() def reset(self): """ -- cgit v1.2.3-54-g00ecf From 1be10f1e0268567c3fc7d19f4b9a1692e8f38fb7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 21 Jan 2019 10:56:31 -0800 Subject: Prevent ReceiveModeRequest.file_write_func from sending a message to the GUI if the request should be closed --- onionshare/web/receive_mode.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index a64c2a73..ad72c9f4 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -348,6 +348,9 @@ class ReceiveModeRequest(Request): """ This function gets called when a specific file is written to. """ + if self.closed: + return + if self.upload_request: self.progress[filename]['uploaded_bytes'] += length -- cgit v1.2.3-54-g00ecf From 87d94f68e89d4fd42e68183a2aee8a2fffef07b7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 21 Jan 2019 11:16:55 -0800 Subject: Don't update the GUI at all untless it has been told about the request --- onionshare/web/receive_mode.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index c319ce13..5e332a39 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -278,8 +278,6 @@ class ReceiveModeRequest(Request): # Don't tell the GUI that a request has started until we start receiving files self.told_gui_about_request = False - self.web.receive_mode.uploads_in_progress.append(self.upload_id) - self.previous_file = None def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None): @@ -294,6 +292,8 @@ class ReceiveModeRequest(Request): 'id': self.upload_id, 'content_length': self.content_length }) + self.web.receive_mode.uploads_in_progress.append(self.upload_id) + self.told_gui_about_request = True self.progress[filename] = { @@ -309,12 +309,13 @@ class ReceiveModeRequest(Request): """ super(ReceiveModeRequest, self).close() try: - upload_id = self.upload_id - # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': upload_id - }) - self.web.receive_mode.uploads_in_progress.remove(upload_id) + if self.told_gui_about_request: + upload_id = self.upload_id + # Inform the GUI that the upload has finished + self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': upload_id + }) + self.web.receive_mode.uploads_in_progress.remove(upload_id) except AttributeError: pass @@ -336,10 +337,11 @@ class ReceiveModeRequest(Request): ), end='') # Update the GUI on the upload progress - self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { - 'id': self.upload_id, - 'progress': self.progress - }) + if self.told_gui_about_request: + self.web.add_request(self.web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'progress': self.progress + }) def file_close_func(self, filename): """ -- cgit v1.2.3-54-g00ecf From 6822c7435fe3687633c630195a7b1cee51c03b73 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 21 Jan 2019 17:11:58 -0800 Subject: Write test to confirm that submitting the receive mode form without selecting files doesn't change in_progress_count or completed_count --- onionshare/web/receive_mode.py | 2 +- tests/GuiReceiveTest.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 5e332a39..30414b77 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -67,7 +67,7 @@ class ReceiveModeWeb(object): receive_mode_dir = os.path.join(self.common.settings.get('data_dir'), date_dir, time_dir) valid = True try: - os.makedirs(receive_mode_dir, 0o700) + os.makedirs(receive_mode_dir, 0o700, exist_ok=True) except PermissionError: self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { "receive_mode_dir": receive_mode_dir diff --git a/tests/GuiReceiveTest.py b/tests/GuiReceiveTest.py index a23a4bc6..8a03283e 100644 --- a/tests/GuiReceiveTest.py +++ b/tests/GuiReceiveTest.py @@ -52,6 +52,28 @@ class GuiReceiveTest(GuiBaseTest): response = requests.get('http://127.0.0.1:{}/close'.format(self.gui.app.port)) self.assertEqual(response.status_code, 404) + def uploading_zero_files_shouldnt_change_ui(self, mode, public_mode): + '''If you submit the receive mode form without selecting any files, the UI shouldn't get updated''' + if not public_mode: + path = 'http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug) + else: + path = 'http://127.0.0.1:{}/upload'.format(self.gui.app.port) + + # What were the counts before submitting the form? + before_in_progress_count = mode.history.in_progress_count + before_completed_count = mode.history.completed_count + before_number_of_history_items = len(mode.history.item_list.items) + + # Click submit without including any files a few times + response = requests.post(path, files={}) + response = requests.post(path, files={}) + response = requests.post(path, files={}) + + # The counts shouldn't change + self.assertEqual(mode.history.in_progress_count, before_in_progress_count) + self.assertEqual(mode.history.completed_count, before_completed_count) + self.assertEqual(len(mode.history.item_list.items), before_number_of_history_items) + def run_receive_mode_sender_closed_tests(self, public_mode): '''Test that the share can be stopped by the sender in receive mode''' if not public_mode: @@ -97,6 +119,7 @@ class GuiReceiveTest(GuiBaseTest): self.counter_incremented(self.gui.receive_mode, 3) self.upload_file(public_mode, '/tmp/testdir/test', 'test') self.counter_incremented(self.gui.receive_mode, 4) + self.uploading_zero_files_shouldnt_change_ui(self.gui.receive_mode, public_mode) self.history_indicator(self.gui.receive_mode, public_mode) self.server_is_stopped(self.gui.receive_mode, False) self.web_server_is_stopped() -- cgit v1.2.3-54-g00ecf From e15f7121022bf346872467a7d3d871b7087cbfe1 Mon Sep 17 00:00:00 2001 From: xin Date: Mon, 21 Jan 2019 16:30:12 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 275fd80a..965b5066 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -4,7 +4,7 @@ "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", "not_a_file": "{0:s} n'est pas un fichier valide.", "other_page_loaded": "Adresse chargée", - "closing_automatically": "Arrêt automatique car le téléchargement est fini", + "closing_automatically": "Arrêté car le transfert est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare démarré", "systray_download_started_message": "Une personne télécharge vos fichiers", @@ -12,7 +12,7 @@ "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "La personne a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", - "help_stay_open": "Continuer le partage après le premier téléchargement", + "help_stay_open": "Continuer le partage après l'envoi des fichiers", "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", @@ -41,7 +41,7 @@ "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", - "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", + "gui_settings_close_after_first_download_option": "Arrêt du partage après l'envoi des fichiers", "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", @@ -169,7 +169,7 @@ "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", "receive_mode_upload_starting": "Un envoi d'une taille totale de {} a commencé", "systray_close_server_message": "Une personne a arrêté le serveur", - "systray_page_loaded_title": "Page OnionShare chargée", + "systray_page_loaded_title": "Page chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", @@ -184,5 +184,29 @@ "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré", "gui_add_files": "Ajouter fichiers", - "gui_add_folder": "Ajouter dossier" + "gui_add_folder": "Ajouter dossier", + "error_cannot_create_data_dir": "Impossible de créer le dossier de données OnionShare : {}", + "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", + "gui_settings_data_dir_label": "Enregistrer les fichiers dans", + "gui_settings_data_dir_browse_button": "Parcourir", + "systray_page_loaded_message": "Adresse OnionShare chargée", + "systray_share_started_title": "Partage commencé", + "systray_share_started_message": "Démarrer l'envoi de fichiers à une personne", + "systray_share_completed_title": "Partage terminé", + "systray_share_canceled_title": "Partage annulé", + "systray_share_canceled_message": "Une personne a annulé la réception de vos fichiers", + "systray_receive_started_title": "Réception commencée", + "systray_receive_started_message": "Une personne vous envoie des fichiers", + "gui_all_modes_history": "Historique", + "gui_all_modes_clear_history": "Tout effacer", + "gui_all_modes_transfer_started": "{} démarré", + "gui_all_modes_transfer_finished_range": "Transféré {} - {}", + "gui_all_modes_transfer_finished": "Transféré {}", + "gui_all_modes_progress_complete": "%p%, {0:s} écoulé.", + "gui_all_modes_progress_starting": "{0:s}, %p% (estimation)", + "gui_all_modes_progress_eta": "{0:s}, Fin : {1:s}, %p%", + "gui_share_mode_no_files": "Pas encore de fichiers envoyés", + "gui_share_mode_timeout_waiting": "En attente de la fin de l'envoi", + "gui_receive_mode_no_files": "Pas encore de fichiers reçus", + "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception" } -- cgit v1.2.3-54-g00ecf From 24707c05465b9ee9bdf304d7581bfa3ed4a1d346 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Mon, 21 Jan 2019 08:03:35 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/share/locale/no.json b/share/locale/no.json index 073461bb..6d5a41f0 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -189,5 +189,30 @@ "timeout_upload_still_running": "Venter på at opplastingen fullføres", "gui_add_files": "Legg til filer", "gui_add_folder": "Legg til mappe", - "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger" + "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger", + "error_cannot_create_data_dir": "Kunne ikke opprette OnionShare-datamappe: {}", + "receive_mode_data_dir": "Filers sendt til deg havner i denne mappen: {}", + "gui_settings_data_dir_label": "Lagre filer i", + "gui_settings_data_dir_browse_button": "Utforsk", + "systray_page_loaded_message": "OnionShare-adresse innlastet", + "systray_share_started_title": "Deling startet", + "systray_share_started_message": "Begynner å sende filer til noen", + "systray_share_completed_title": "Deling fullført", + "systray_share_completed_message": "Forsendelse av filer utført", + "systray_share_canceled_title": "Deling avbrutt", + "systray_share_canceled_message": "Noen avbrøt mottak av filene dine", + "systray_receive_started_title": "Mottak startet", + "systray_receive_started_message": "Noen sender filer til deg", + "gui_all_modes_history": "Historikk", + "gui_all_modes_clear_history": "Tøm alt", + "gui_all_modes_transfer_started": "Startet {}", + "gui_all_modes_transfer_finished_range": "Overført {} - {}", + "gui_all_modes_transfer_finished": "Overført {}", + "gui_all_modes_progress_complete": "%p%, {0:s} forløpt.", + "gui_all_modes_progress_starting": "{0:s}, %p% (kalkulerer)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Ingen filer sendt enda", + "gui_share_mode_timeout_waiting": "Venter på fullføring av forsendelse", + "gui_receive_mode_no_files": "Ingen filer mottatt enda", + "gui_receive_mode_timeout_waiting": "Venter på fullføring av mottak" } -- cgit v1.2.3-54-g00ecf From 498a2ca41fd71768bc896a7d2b79b18a5d440468 Mon Sep 17 00:00:00 2001 From: R Date: Mon, 21 Jan 2019 11:49:00 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 133 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 54 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index bca7cc99..9c266770 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -8,10 +8,10 @@ "ctrlc_to_stop": "برای توقف سرور Ctrl+C را فشار دهید", "not_a_file": "{0:s} یک فایل معتبر نمی باشد.", "not_a_readable_file": "{0:s} قابل خواندن نمی باشد.", - "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد.", + "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد", "other_page_loaded": "آدرس بارگذاری شد", - "close_on_timeout": "", - "closing_automatically": "متوقف شد چون دانلود به پایان رسید", + "close_on_timeout": "متوقف شد چون تایمر توقف خودکار به پایان رسید", + "closing_automatically": "متوقف شد چون انتقال انجام شد", "timeout_download_still_running": "انتظار برای تکمیل دانلود", "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد", "systray_menu_exit": "خروج", @@ -23,26 +23,26 @@ "systray_download_canceled_message": "کاربر دانلود را لغو کرد", "systray_upload_started_title": "آپلود OnionShare آغاز شد", "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است", - "help_local_only": "", - "help_stay_open": "ادامه اشتراک گذاری پس از اولین دانلود", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", + "help_local_only": "عدم استفاده از Tor (فقط برای توسعه)", + "help_stay_open": "ادامه اشتراک گذاری پس از ارسال دانلود ها", + "help_shutdown_timeout": "توقف به اشتراک گذاری پس از میزان ثانیه ای مشخص", + "help_stealth": "استفاده از احراز هویت کلاینت (پیشرفته)", + "help_receive": "دریافت اشتراک به جای ارسال آن", + "help_debug": "لاگ کردن خطاهای OnionShare روی stdout، و خطاهای وب بر روی دیسک", "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری", - "help_config": "", - "gui_drag_and_drop": "", + "help_config": "مکان فایل کانفیگ JSON کاستوم (اختیاری)", + "gui_drag_and_drop": "فایل ها و پوشه ها را بکشید و رها کنید\nتا اشتراک گذاری آغاز شود", "gui_add": "افزودن", "gui_delete": "حذف", "gui_choose_items": "انتخاب", "gui_share_start_server": "شروع اشتراک گذاری", "gui_share_stop_server": "توقف اشتراک گذاری", "gui_share_stop_server_shutdown_timeout": "توقف اشتراک گذاری ({} ثانیه باقیمانده)", - "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} متوقف می شود", "gui_receive_start_server": "شروع حالت دریافت", "gui_receive_stop_server": "توقف حالت دریافت", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_stop_server_shutdown_timeout": "توقف حالت دریافت ({} ثانیه باقیمانده)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} به پایان می رسد", "gui_copy_url": "کپی آدرس", "gui_copy_hidservauth": "کپی HidServAuth", "gui_downloads": "دانلود تاریخچه", @@ -56,31 +56,31 @@ "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", - "version_string": "", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "نه به این سرعت", "gui_share_quit_warning": "شما در پروسه ارسال فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_receive_quit_warning": "شما در پروسه دریافت فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_quit_warning_quit": "خروج", "gui_quit_warning_dont_quit": "لغو", - "error_rate_limit": "", + "error_rate_limit": "شخصی تعداد زیادی قصد ناصحیح روی آدرس شما داشته است، این می تواند بدین معنا باشد که در حال تلاش برای حدس زدن آن هستند، بنابراین OnionShare سرور را متوقف کرده است. دوباره اشتراک گذاری را آغاز کنید و به گیرنده یک آدرس جدید برای اشتراک ارسال کنید.", "zip_progress_bar_format": "فشرده سازی: %p%", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_stealth_not_supported": "برای استفاده از احراز هویت کلاینت، شما نیاز به داشتن Tor 0.2.9.1-alpha (یا مرورگر Tor 6.5) و python3-stem 1.5.0 دارید.", + "error_ephemeral_not_supported": "OnionShare حداقل به Tor 0.2.7.1 و python3-stem 1.4.0 نیاز دارد.", "gui_settings_window_title": "تنظیمات", "gui_settings_whats_this": "این چیست؟", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "gui_settings_stealth_option": "استفاده از احراز هویت کلاینت", + "gui_settings_stealth_hidservauth_string": "ذخیره کردن کلید خصوصی برای استفاده دوباره، بدین معناست که الان می توانید\nبرای کپی HidServAuth کلیک کنید.", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp": "آخرین بررسی: {}", "gui_settings_autoupdate_timestamp_never": "هرگز", "gui_settings_autoupdate_check_button": "بررسی برای نسخه جدید", "gui_settings_general_label": "تنظیمات کلی", "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", - "gui_settings_close_after_first_download_option": "توقف اشتراک پس از اولین دانلود", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", + "gui_settings_close_after_first_download_option": "توقف اشتراک گذاری پس از اولین ارسال دانلود", + "gui_settings_connection_type_label": "OnionShare چگونه به Tor باید متصل شود؟", + "gui_settings_connection_type_bundled_option": "استفاده از نسخه Tor قرار گرفته در OnionShare", + "gui_settings_connection_type_automatic_option": "اعمال پیکربندی خودکار با مرورگر Tor", "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل", "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت", "gui_settings_connection_type_test_button": "تست اتصال به Tor", @@ -88,16 +88,16 @@ "gui_settings_socket_file_label": "فایل سوکت‌", "gui_settings_socks_label": "پورت SOCKS", "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_no_auth_option": "هیچ احراز هویت، یا احراز هویت کوکی", "gui_settings_authenticate_password_option": "رمز عبور", "gui_settings_password_label": "رمز عبور", - "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges": "پشتیبانی بریج Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_obfs4_radio_option": "استفاده از پلاگبل ترنسپورت obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت obfs4 (نیازمند obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "استفاده از پلاگبل ترنسپورت meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت meek_lite (Azure) (نیازمند obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "هشدار: بریج های meek_lite برای پروژه Tor بسیار هزینه بر هستند.

    فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق obfs4، یا دیگر بریج ها از آن استفاده کنید.", "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم", "gui_settings_tor_bridges_custom_label": "میتوانید از https://bridges.torproject.org بریج دریافت کنید", "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.", @@ -106,24 +106,24 @@ "gui_settings_button_help": "راهنما", "gui_settings_shutdown_timeout_checkbox": "استفاده از تایمر توقف خودکار", "gui_settings_shutdown_timeout": "توقف اشتراک در:", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", + "settings_error_unknown": "ناتوانی در اتصال به کنترل کننده Tor بدلیل نامفهوم بودن تنظیمات.", + "settings_error_automatic": "ناتوانی در اتصال به کنترل کننده Tor. آیا مرورگر Tor (در دسترس از طریق torproject.org) در پس زمینه در حال اجراست؟", + "settings_error_socket_port": "ناتوانی در اتصال به کنترل کننده Tor در {}:{}.", + "settings_error_socket_file": "ناتوانی در اتصال به کنترل کننده Tor از طریق فایل سوکت {}.", + "settings_error_auth": "متصل به {}:{}، اما ناتوانی در احراز هویت. شاید این یک کنترل کننده Tor نمی باشد؟", + "settings_error_missing_password": "متصل به کنترل کننده Tor، اما نیاز به یک رمز عبور برای احراز هویت می باشد.", + "settings_error_unreadable_cookie_file": "اتصال به کنترل کننده Tor برقرار است، اما رمز عبور ممکن است اشتباه باشد، یا کاربری شما اجازه خواندن فایل کوکی را ندارد.", + "settings_error_bundled_tor_not_supported": "استفاده از نسخه Tor که با OnionShare می آید در حالت توسعه روی ویندوز یا مک کار نمی کند.", + "settings_error_bundled_tor_timeout": "اتصال به Tor زمان زیادی می برد. شاید شما به اینترنت متصل نیستید، یا ساعت سیستم شما دقیق نیست؟", + "settings_error_bundled_tor_broken": "OnionShare نمی تواند در پس زمینه به Tor متصل شود:\n{}", + "settings_test_success": "اتصال به کنترل کننده Tor برقرار است.\n\nنسخه Tor: {}\nسرویس های onion ناپایدار پشتیبانی شده: {}.\nاحراز هویت کلاینت پشتیبانی شده: {}.\nپشتیبانی از آدرس های .onion نسل بعدی: {}.", + "error_tor_protocol_error": "خطایی با Tor وجود داشت: {}", + "error_tor_protocol_error_unknown": "خطای ناشناخته ای با Tor وجود داشت", "error_invalid_private_key": "این نوع کلید خصوصی پشتیبانی نمی شود", "connecting_to_tor": "در حال اتصال به شبکه Tor", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", + "update_available": "نسخه جدید OnionShare وجود دارد. اینجا کلیک کنید تا آن را دریافت کنید.

    شما در حال استفاده از {} می باشید و آخرین نسخه {} می باشد.", + "update_error_check_error": "ناتوانی در بررسی برای نسخه های جدید: سایت OnionShare میگوید که آخرین نسخه '{}' ناشناس می باشد…", + "update_error_invalid_latest_version": "ناتوانی در بررسی نسخه جدید: شاید شما به Tor متصل نیستید، یا سایت OnionShare کار نمی کند؟", "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.", "gui_tor_connection_ask": "باز کردن تنظیمات برای ساماندهی اتصال به Tor؟", "gui_tor_connection_ask_open_settings": "بله", @@ -136,12 +136,12 @@ "share_via_onionshare": "OnionShare کنید", "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده", "gui_save_private_key_checkbox": "استفاده از یک آدرس پایا", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", + "gui_share_url_description": "هرکس با این آدرس OnionShare میتواند روی کامپیوتر شما فایل دانلود کند از طریق مرورگر تور: ", + "gui_receive_url_description": "هرکس با این آدرس OnionShare میتواند روی کامپیوتر شما فایل آپلود کند از طریق مرورگر تور: ", + "gui_url_label_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.

    هر اشتراک بعدی از آدرس دوباره استفاده می کند. ( برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)", "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.", "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.", - "gui_url_label_onetime_and_persistent": "", + "gui_url_label_onetime_and_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.

    هر اشتراک بعدی از آدرس دوباره استفاده میکند. (برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)", "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری", "gui_status_indicator_share_working": "در حال شروع…", "gui_status_indicator_share_started": "در حال اشتراک گذاری", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "حالت عمومی", "systray_close_server_title": "سرور OnionShare بسته شد", "systray_close_server_message": "یک کاربر سرور را بست", - "systray_page_loaded_title": "صفحه OnionShare بارگذاری شد", + "systray_page_loaded_title": "صفحه بارگذاری شد", "systray_download_page_loaded_message": "یک کاربر صفحه دانلود را بارگذاری کرد", "systray_upload_page_loaded_message": "یک کاربر صفحه آپلود را بارگذاری کرد", "gui_uploads": "تاریخچه آپلود", @@ -185,5 +185,30 @@ "timeout_upload_still_running": "انتظار برای تکمیل آپلود", "gui_add_files": "افزودن فایل ها", "gui_add_folder": "افزودن پوشه", - "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion" + "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion", + "error_cannot_create_data_dir": "ناتوانی در ایجاد پوشه داده OnionShare: {}", + "receive_mode_data_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}", + "gui_settings_data_dir_label": "ذخیره فایل ها در", + "gui_settings_data_dir_browse_button": "مرور", + "systray_page_loaded_message": "آدرس OnionShare بارگذاری شد", + "systray_share_started_title": "اشتراک گذاری آغاز شد", + "systray_share_started_message": "آغاز ارسال فایل به شخصی", + "systray_share_completed_title": "اشتراک گذاری تکمیل شد", + "systray_share_completed_message": "ارسال فایل ها به پایان رسید", + "systray_share_canceled_title": "اشتراک گذاری لغو شد", + "systray_share_canceled_message": "شخصی دریافت فایل های شما را لغو کرد", + "systray_receive_started_title": "دریافت آغاز شد", + "systray_receive_started_message": "شخصی در حال ارسال فایل به شماست", + "gui_all_modes_history": "تاریخچه", + "gui_all_modes_clear_history": "پاکسازی همه", + "gui_all_modes_transfer_started": "{} شروع شد", + "gui_all_modes_transfer_finished_range": "منتقل شد {} - {}", + "gui_all_modes_transfer_finished": "{} منتقل شد", + "gui_all_modes_progress_complete": "%p%، {0:s} سپری شد.", + "gui_all_modes_progress_starting": "{0:s}, %p% (در حال محاسبه)", + "gui_all_modes_progress_eta": "{0:s}، تخمین: {1:s}, %p%", + "gui_share_mode_no_files": "هیچ فایلی هنوز ارسال نشده است", + "gui_share_mode_timeout_waiting": "انتظار برای به پایان رسیدن ارسال", + "gui_receive_mode_no_files": "هیچ فایلی هنوز دریافت نشده است", + "gui_receive_mode_timeout_waiting": "انتظار برای به پایان رسیدن دریافت" } -- cgit v1.2.3-54-g00ecf From c96b653de84801b5fc8b64cc31ac2d64395d8107 Mon Sep 17 00:00:00 2001 From: Zuhualime Akoochimoya Date: Mon, 21 Jan 2019 10:16:34 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/share/locale/es.json b/share/locale/es.json index ce6c0fd2..ed73bd1e 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -4,9 +4,9 @@ "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo válido.", "other_page_loaded": "La URL está lista", - "closing_automatically": "Apagando automáticamente porque la descarga finalizó", + "closing_automatically": "Detenido porque la transferencia se completó", "help_local_only": "No usar Tor (sólo para desarrollo)", - "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", + "help_stay_open": "Continuar compartiendo luego que los archivos hayan sido enviados", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir", @@ -47,7 +47,7 @@ "gui_settings_tor_bridges": "Soporte de puentes de Tor", "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", "settings_saved": "Ajustes guardados en {}", - "give_this_url_receive": "Dale esta dirección al remitente:", + "give_this_url_receive": "Dele esta dirección al remitente:", "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:", "not_a_readable_file": "{0:s} no es un archivo legible.", "systray_menu_exit": "Salir", @@ -132,7 +132,7 @@ "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_general_label": "Ajustes generales", "gui_settings_sharing_label": "Configuración de compartición", - "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", + "gui_settings_close_after_first_download_option": "Dejar de compartir luego que los archivos hayan sido enviados", "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket", @@ -172,7 +172,7 @@ "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare cerrado", "systray_close_server_message": "Un usuario cerró el servidor", - "systray_page_loaded_title": "Página de OnionShare Cargada", + "systray_page_loaded_title": "Página Cargada", "systray_download_page_loaded_message": "Un usuario cargó la página de descarga", "systray_upload_page_loaded_message": "Un usuario cargó la página de carga", "gui_uploads": "Historial de carga", @@ -189,5 +189,30 @@ "timeout_upload_still_running": "Esperando a que se complete la subida", "gui_add_files": "Añadir Archivos", "gui_add_folder": "Añadir Carpeta", - "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla" + "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla", + "error_cannot_create_data_dir": "No se pudo crear carpeta de datos OnionShare: {}", + "receive_mode_data_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", + "gui_settings_data_dir_label": "Guardar archivos en", + "gui_settings_data_dir_browse_button": "Navegar", + "systray_page_loaded_message": "Dirección OnionShare cargada", + "systray_share_started_title": "Compartir Iniciado", + "systray_share_started_message": "Se empezó a enviar archivos a alguien", + "systray_share_completed_title": "Compartir Completado", + "systray_share_completed_message": "Finalizó envío de archivos", + "systray_share_canceled_title": "Compartir Cancelado", + "systray_share_canceled_message": "Alguien canceló la recepción de sus archivos", + "systray_receive_started_title": "Recepción Iniciada", + "systray_receive_started_message": "Alguien le está enviando archivos", + "gui_all_modes_history": "Historial", + "gui_all_modes_clear_history": "Limpiar Todo", + "gui_all_modes_transfer_started": "Iniciado {}", + "gui_all_modes_transfer_finished_range": "Transferido {} - {}", + "gui_all_modes_transfer_finished": "Transferido {}", + "gui_all_modes_progress_complete": "%p%, {0:s} transcurridos.", + "gui_all_modes_progress_starting": "{0:s}, %p% (calculando)", + "gui_all_modes_progress_eta": "{0:s}, TEA: {1:s}, %p%", + "gui_share_mode_no_files": "No se enviaron archivos todavía", + "gui_share_mode_timeout_waiting": "Esperando a que termine el envío", + "gui_receive_mode_no_files": "No se recibieron archivos todavía", + "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción" } -- cgit v1.2.3-54-g00ecf From 2fcedb8730c70e15e940dee9d2e346b46574a20f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 21 Jan 2019 17:32:58 -0800 Subject: Oops, finish resolving merge conflict --- onionshare/web/receive_mode.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 450417f6..6124ecf1 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -347,29 +347,21 @@ class ReceiveModeRequest(Request): self.web.common.log('ReceiveModeRequest', 'close') try: -<<<<<<< HEAD - upload_id = self.upload_id - - if not self.web.stop_q.empty(): - # Inform the GUI that the upload has canceled - self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { - 'id': upload_id - }) - else: -======= if self.told_gui_about_request: upload_id = self.upload_id ->>>>>>> develop - # Inform the GUI that the upload has finished - self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { - 'id': upload_id - }) -<<<<<<< HEAD - self.web.receive_mode.uploads_in_progress.remove(upload_id) -======= + if not self.web.stop_q.empty(): + # Inform the GUI that the upload has canceled + self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { + 'id': upload_id + }) + else: + # Inform the GUI that the upload has finished + self.web.add_request(self.web.REQUEST_UPLOAD_FINISHED, self.path, { + 'id': upload_id + }) self.web.receive_mode.uploads_in_progress.remove(upload_id) ->>>>>>> develop + except AttributeError: pass -- cgit v1.2.3-54-g00ecf From f854b3ff372597664d4ef5dce0f6ba0d3574b35f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 21 Jan 2019 17:43:13 -0800 Subject: Tests are failing because a receive mode dir already exists, so this makes them pass --- onionshare/web/receive_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 6124ecf1..f035271a 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -137,7 +137,7 @@ class ReceiveModeWeb(object): # Make sure receive mode dir exists before writing file valid = True try: - os.makedirs(receive_mode_dir, 0o700) + os.makedirs(receive_mode_dir, 0o700, exist_ok=True) except PermissionError: self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { "receive_mode_dir": receive_mode_dir -- cgit v1.2.3-54-g00ecf From 732042e53e7ed84ceb1ffe196bc61bb9a38b55e8 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Wed, 23 Jan 2019 06:19:17 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index fdb456e0..48090c82 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -9,7 +9,7 @@ "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten", "other_page_loaded": "Adresse indlæst", "close_on_timeout": "Stoppede fordi timer med autostop løb ud", - "closing_automatically": "Stoppede fordi download er færdig", + "closing_automatically": "Stoppede fordi overførslen er færdig", "timeout_download_still_running": "Venter på at download skal blive færdig", "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling", "systray_menu_exit": "Afslut", @@ -20,7 +20,7 @@ "systray_download_canceled_title": "OnionShare-download annulleret", "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Brug ikke Tor (kun til udvikling)", - "help_stay_open": "Bliv ved med at dele efter første download", + "help_stay_open": "Fortsæt deling efter filerne er blevet sendt", "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", "help_stealth": "Brug klientautentifikation (avanceret)", "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", @@ -59,7 +59,7 @@ "gui_settings_autoupdate_timestamp_never": "Aldrig", "gui_settings_autoupdate_check_button": "Søg efter ny version", "gui_settings_sharing_label": "Delingsindstillinger", - "gui_settings_close_after_first_download_option": "Stop deling efter første download", + "gui_settings_close_after_first_download_option": "Stop deling efter filerne er blevet sendt", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", "gui_settings_connection_type_bundled_option": "Brug Tor-versionen som er indbygget i OnionShare", "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser", @@ -162,7 +162,7 @@ "gui_settings_public_mode_checkbox": "Offentlig tilstand", "systray_close_server_title": "OnionShare-server lukket", "systray_close_server_message": "En bruger lukkede serveren", - "systray_page_loaded_title": "OnionShare-side indlæst", + "systray_page_loaded_title": "Siden er indlæst", "systray_download_page_loaded_message": "En bruger indlæste downloadsiden", "systray_upload_page_loaded_message": "En bruger indlæste uploadsiden", "gui_uploads": "Uploadhistorik", @@ -188,5 +188,30 @@ "timeout_upload_still_running": "Venter på at upload skal blive færdig", "gui_add_files": "Tilføj filer", "gui_add_folder": "Tilføj mappe", - "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste" + "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste", + "error_cannot_create_data_dir": "Kunne ikke oprette OnionShare-datamappe: {}", + "receive_mode_data_dir": "Filer som sendes til dig vises i denne mappe: {}", + "gui_settings_data_dir_label": "Gem filer til", + "gui_settings_data_dir_browse_button": "Gennemse", + "systray_page_loaded_message": "OnionShare-adresse indlæst", + "systray_share_started_title": "Deling startet", + "systray_share_started_message": "Start på at sende filer til nogen", + "systray_share_completed_title": "Deling er færdig", + "systray_share_completed_message": "Færdig med at sende filer", + "systray_share_canceled_title": "Deling annulleret", + "systray_share_canceled_message": "Nogen annullerede modtagelsen af dine filer", + "systray_receive_started_title": "Modtagelse startede", + "systray_receive_started_message": "Nogen sender filer til dig", + "gui_all_modes_history": "Historik", + "gui_all_modes_clear_history": "Ryd alle", + "gui_all_modes_transfer_started": "Startede {}", + "gui_all_modes_transfer_finished_range": "Overførte {} - {}", + "gui_all_modes_transfer_finished": "Overførte {}", + "gui_all_modes_progress_complete": "%p%, {0:s} forløbet.", + "gui_all_modes_progress_starting": "{0:s}, %p% (udregner)", + "gui_all_modes_progress_eta": "{0:s}, anslået ankomsttidspunkt: {1:s}, %p%", + "gui_share_mode_no_files": "Der er endnu ikke sendt nogen filer", + "gui_share_mode_timeout_waiting": "Venter på at blive færdig med at sende", + "gui_receive_mode_no_files": "Der er endnu ikke modtaget nogen filer", + "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage" } -- cgit v1.2.3-54-g00ecf From 02fb115e5f1d0563adeed663828ad3b2cc6c297d Mon Sep 17 00:00:00 2001 From: emma peel Date: Wed, 23 Jan 2019 11:04:24 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ --- share/locale/zh_Hans.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index 958bfb3c..16ca1064 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -1,12 +1,12 @@ { - "config_onion_service": "", + "config_onion_service": "在端口{0:d}上设置洋葱服务。", "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", + "give_this_url": "把这个地址给收件人:", + "give_this_url_stealth": "向收件人提供此地址和HidServAuth行:", + "give_this_url_receive": "把这个地址交给发件人:", + "give_this_url_receive_stealth": "把这个地址和HidServAuth交给发送者:", + "ctrlc_to_stop": "按Ctrl+C停止服务器", + "not_a_file": "{0:s}不是有效文件。", "not_a_readable_file": "", "no_available_port": "", "other_page_loaded": "", -- cgit v1.2.3-54-g00ecf From 90f99ae7124991e79757d3bb2e6c8134c6c96026 Mon Sep 17 00:00:00 2001 From: Ethan Sayyid Date: Wed, 23 Jan 2019 11:13:49 +0000 Subject: Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ --- share/locale/zh_Hans.json | 280 +++++++++++++++++++++++++--------------------- 1 file changed, 154 insertions(+), 126 deletions(-) diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index 16ca1064..2331e762 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -1,19 +1,19 @@ { "config_onion_service": "在端口{0:d}上设置洋葱服务。", - "preparing_files": "", + "preparing_files": "正在压缩文件.", "give_this_url": "把这个地址给收件人:", "give_this_url_stealth": "向收件人提供此地址和HidServAuth行:", "give_this_url_receive": "把这个地址交给发件人:", "give_this_url_receive_stealth": "把这个地址和HidServAuth交给发送者:", "ctrlc_to_stop": "按Ctrl+C停止服务器", "not_a_file": "{0:s}不是有效文件。", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", + "not_a_readable_file": "{0:s}不是可读文件.", + "no_available_port": "找不到可用于开启onion服务的端口", + "other_page_loaded": "地址已加载完成", + "close_on_timeout": "终止 原因:自动停止计时器的时间已到", + "closing_automatically": "终止 原因:传输已完成", "timeout_download_still_running": "", - "large_filesize": "", + "large_filesize": "警告:分享大文件可能会用上数小时", "systray_menu_exit": "退出", "systray_download_started_title": "", "systray_download_started_message": "", @@ -23,153 +23,153 @@ "systray_download_canceled_message": "", "systray_upload_started_title": "", "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", + "help_local_only": "不使用Tor(只限开发测试)", + "help_stay_open": "文件传输完成后继续分享", + "help_shutdown_timeout": "超过给定时间(秒)后,终止分享.", + "help_stealth": "使用服务端认证(高级选项)", + "help_receive": "仅接收分享的文件,不发送", + "help_debug": "将OnionShare错误日志记录到stdout,将web错误日志记录到磁盘", + "help_filename": "要分享的文件或文件夹的列表", + "help_config": "自定义JSON配置文件的路径(可选)", + "gui_drag_and_drop": "将文件或文件夹拖动到这里来开始分享", "gui_add": "添加", "gui_delete": "删除", - "gui_choose_items": "选择", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", + "gui_choose_items": "选取", + "gui_share_start_server": "开始分享", + "gui_share_stop_server": "停止分享", + "gui_share_stop_server_shutdown_timeout": "停止分享(还剩{}秒)", + "gui_share_stop_server_shutdown_timeout_tooltip": "在{}自动停止", + "gui_receive_start_server": "开启接受模式", + "gui_receive_stop_server": "停止接受模式", + "gui_receive_stop_server_shutdown_timeout": "停止接受模式(还剩{}秒)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "在{}自动停止", + "gui_copy_url": "复制地址", + "gui_copy_hidservauth": "复制HidServAuth", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "取消", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_canceled": "已取消", + "gui_copied_url_title": "已复制的OnionShare地址", + "gui_copied_url": "OnionShare地址已复制到剪贴板", + "gui_copied_hidservauth_title": "已复制的HidServAuth", + "gui_copied_hidservauth": "HidServAuth行已复制到剪贴板", + "gui_please_wait": "起始中...点击这里可取消.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "version_string": "版本: OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "再等等", + "gui_share_quit_warning": "您有文件正在传输中...您确定要退出OnionShare吗?", + "gui_receive_quit_warning": "您有文件还正在接收中...您确定要退出OnionShare吗?", "gui_quit_warning_quit": "退出", "gui_quit_warning_dont_quit": "取消", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "设置中", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "error_rate_limit": "有人您对地址发出过多错误请求,这很可能说明有人在尝试猜测您的地址.因此为了安全OinionShare已终止服务.请重新开启分享并且向收件人发送新地址.", + "zip_progress_bar_format": "压缩中: %p%", + "error_stealth_not_supported": "要使用服务端认证,您至少需要的最低版本要求是:Tor 0.2.9.1-alpha (or Tor Browser 6.5)和python3-stem 1.5.0.两者缺一不可,同时需要.", + "error_ephemeral_not_supported": "OnionShare至少同时需要Tor 0.2.7.1和python3-stem 1.4.0来运行.", + "gui_settings_window_title": "设置", + "gui_settings_whats_this": "这是什么?", + "gui_settings_stealth_option": "使用客户端认证", + "gui_settings_stealth_hidservauth_string": "已保存了你的私钥用于重复使用,意味着您现在可以\n点击这里来复制您的HidServAuth.", "gui_settings_autoupdate_label": "检查新版本", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_option": "有新版本可用时告知我", + "gui_settings_autoupdate_timestamp": "上次检查更新的时间:{}", "gui_settings_autoupdate_timestamp_never": "从不", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "检查新版本", "gui_settings_general_label": "常规设置", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", + "gui_settings_sharing_label": "分享设置", + "gui_settings_close_after_first_download_option": "文件发送完成后停止分享", + "gui_settings_connection_type_label": "OnionShare应如何连接Tor?", + "gui_settings_connection_type_bundled_option": "使用OnionShare内置的tor", + "gui_settings_connection_type_automatic_option": "尝试使用Tor Browser(Tor浏览器)的设置", + "gui_settings_connection_type_control_port_option": "用特定端口连接", + "gui_settings_connection_type_socket_file_option": "使用socket文档的设置连接", + "gui_settings_connection_type_test_button": "测试tor连接", "gui_settings_control_port_label": "控制端口", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "Socket配置文档", + "gui_settings_socks_label": "SOCKS 端口", + "gui_settings_authenticate_label": "Tor认证设置", + "gui_settings_authenticate_no_auth_option": "无须认证,或者使用的是cookie认证", "gui_settings_authenticate_password_option": "密码", "gui_settings_password_label": "密码", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_tor_bridges": "Tor网桥设置", + "gui_settings_tor_bridges_no_bridges_radio_option": "不使用网桥", + "gui_settings_tor_bridges_obfs4_radio_option": "使用内置的obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "使用内置的obfs4 pluggable transports(需要obfs4代理)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "使用内置meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "使用内置meek_lite (Azure) pluggable transports (需要obfs4代理)", + "gui_settings_meek_lite_expensive_warning": "警告:meek_lite类型的网桥对Tor流量产生的负担很大,
    请只在无法直接使用tor,且obfs4 transport和其他网桥都无法连接时才使用.
    .", + "gui_settings_tor_bridges_custom_radio_option": "使用自定义网桥", + "gui_settings_tor_bridges_custom_label": "您可以从这里得到网桥地址\nhttps://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "您所添加的网桥无法工作.\n请双击它们或者添加其它网桥.", "gui_settings_button_save": "保存", "gui_settings_button_cancel": "取消", "gui_settings_button_help": "帮助", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "gui_settings_shutdown_timeout_checkbox": "使用自动停止计时器", + "gui_settings_shutdown_timeout": "在(时间)停止分享", + "settings_error_unknown": "无法连接Tor控制件,因为您的设置无法被理解.", + "settings_error_automatic": "无法连接tor控制件.Tor浏览器是否在后台工作?(从torproject.org可以获得Tor Browser)", + "settings_error_socket_port": "在socket端口{}:{}无法连接tor控制件.", + "settings_error_socket_file": "无法使用socket配置文档的设置连接tor控制件", + "settings_error_auth": "已连接到了{}:{},但是无法认证,也许这不是tor控制件?", + "settings_error_missing_password": "已连接到tor控制件,但需要密码来认证.", + "settings_error_unreadable_cookie_file": "已连接到tor控制件,但可能密码错误,或者没有读取cookie文件的权限.", + "settings_error_bundled_tor_not_supported": "OnionShare自带的Tor无法在Windows或macOS下运行开发者模式", + "settings_error_bundled_tor_timeout": "尝试连接tor的用时过长,也许您的网络有问题,或者是系统时间不准确?", + "settings_error_bundled_tor_broken": "OnionShare无法在后台连接Tor\n{}", + "settings_test_success": "已连接到Tor控制件\n\nTor版本: {}\n支持短期onion服务: {}.\n支持客户端认证: {}.\n支持新一代.onion地址: {}.", + "error_tor_protocol_error": "Tor出现错误: {}", + "error_tor_protocol_error_unknown": "Tor出现未知错误", + "error_invalid_private_key": "不支持这种类型的私钥", + "connecting_to_tor": "正在连接Tor网络", + "update_available": "有新版本的OnionShare可用:请点击这里 来获得.

    您在使用的版本为 {} 最新的可用版本为 {}.", + "update_error_check_error": "无法检查更新:OnionShare官网对最新版本无法识别'{}'…", + "update_error_invalid_latest_version": "无法检查更新:也许您没有连接到Tor?或者OnionShare官网不可用?", + "update_not_available": "您现在运行的OnionShare为最新版本.", + "gui_tor_connection_ask": "打开设置来查看Tor连接?", "gui_tor_connection_ask_open_settings": "是的", "gui_tor_connection_ask_quit": "退出", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "分享", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", + "gui_tor_connection_error_settings": "请尝试在设置中设定OnionShare连接Tor的方式.", + "gui_tor_connection_canceled": "无法连接Tor.\n\n请确保您一连接到网络,然后重启OnionShare并设置Tor连接.", + "gui_tor_connection_lost": "已和Tor断开连接.", + "gui_server_started_after_timeout": "在服务开始之前自动停止计时器的时间已到.\n请建立新的分享.", + "gui_server_timeout_expired": "自动停止计时器的时间已到.\n请更新其设置来开始分享.", + "share_via_onionshare": "用OnionShare来分享", + "gui_use_legacy_v2_onions_checkbox": "使用古老的地址", + "gui_save_private_key_checkbox": "使用长期地址", + "gui_share_url_description": "任何人只要拥有这个OnionShare 地址,都可以用Tor浏览器来从您的设备进行文件下载", + "gui_receive_url_description": "任何人只要拥有这个OnionShare 地址,都可以用Tor浏览器来给你的设备进行文件上传", + "gui_url_label_persistent": "这个分享不会自动停止.

    每个子列分享都会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)", + "gui_url_label_stay_open": "这个分享不会自动停止.", + "gui_url_label_onetime": "这个分享将在初次完成后终止.", + "gui_url_label_onetime_and_persistent": "这个分享不会自动停止.

    每个子列分享都将会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)", + "gui_status_indicator_share_stopped": "准备分享", + "gui_status_indicator_share_working": "正在初始话…", + "gui_status_indicator_share_started": "分享中", + "gui_status_indicator_receive_stopped": "准备接收", + "gui_status_indicator_receive_working": "正在初始化…", "gui_status_indicator_receive_started": "正在接收", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "gui_file_info": "{} 个文件, {}", + "gui_file_info_single": "{} 个文件, {}", + "history_in_progress_tooltip": "{}在进行中", + "history_completed_tooltip": "{}已完成", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "警告:接收模式下允许他人对您的设备上传文件.有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.", + "gui_receive_mode_warning": "接收模式下允许他人对您的设备上传文件.

    有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.", + "receive_mode_upload_starting": "上传文件的大小为{}正在开始", + "receive_mode_received_file": "接收到: {}", + "gui_mode_share_button": "分享文件", + "gui_mode_receive_button": "接收文件", + "gui_settings_receiving_label": "接收设置", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "分享轨迹", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "公共模式", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "页面已加载", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,7 +179,35 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", + "gui_open_folder_error_nautilus": "无法打开文件夹,原因:nautilus不可用.文件在这里: {}", "gui_settings_language_label": "首选语言", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "请重启OnionShare以使您的语言改变设定生效.", + "gui_add_files": "添加文件", + "gui_add_folder": "添加文件夹", + "gui_connect_to_tor_for_onion_settings": "连接Tor来查看onion服务的设置", + "error_cannot_create_data_dir": "无法建立OnionShare文件夹: {}", + "receive_mode_data_dir": "您收到的文件会出现在这个文件夹: {}", + "gui_settings_data_dir_label": "将文件保存到", + "gui_settings_data_dir_browse_button": "浏览", + "systray_page_loaded_message": "OnionShare地址已加载", + "systray_share_started_title": "分享开始", + "systray_share_started_message": "开始向某人发送文件", + "systray_share_completed_title": "分享完成", + "systray_share_completed_message": "文件发送完成", + "systray_share_canceled_title": "分享已取消", + "systray_share_canceled_message": "某人取消了接收您的文件", + "systray_receive_started_title": "接收开始", + "systray_receive_started_message": "某人在向您发送文件", + "gui_all_modes_history": "历史", + "gui_all_modes_clear_history": "清除全部", + "gui_all_modes_transfer_started": "已开始{}", + "gui_all_modes_transfer_finished_range": "已传输 {} - {}", + "gui_all_modes_transfer_finished": "已传输完成 {}", + "gui_all_modes_progress_complete": "%p%, {0:s} 已完成.", + "gui_all_modes_progress_starting": "{0:s}, %p% (计算中)", + "gui_all_modes_progress_eta": "{0:s}, 预计完成时间: {1:s}, %p%", + "gui_share_mode_no_files": "还没有文件发出", + "gui_share_mode_timeout_waiting": "等待结束发送", + "gui_receive_mode_no_files": "还没有接收文件", + "gui_receive_mode_timeout_waiting": "等待接收完成" } -- cgit v1.2.3-54-g00ecf From 2d1e97b06556de83c9af99b40e1cff6c29643c6c Mon Sep 17 00:00:00 2001 From: michalis Date: Tue, 22 Jan 2019 12:35:15 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 965b5066..0adbf96a 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -208,5 +208,7 @@ "gui_share_mode_no_files": "Pas encore de fichiers envoyés", "gui_share_mode_timeout_waiting": "En attente de la fin de l'envoi", "gui_receive_mode_no_files": "Pas encore de fichiers reçus", - "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception" + "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception", + "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service Onion", + "systray_share_completed_message": "Terminé l'envoi de fichiers" } -- cgit v1.2.3-54-g00ecf From a33219aa6255bc1093c80f6833c09e359ddb40b4 Mon Sep 17 00:00:00 2001 From: LocLab fr Date: Fri, 25 Jan 2019 14:16:29 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 90 ++++++++++++++++++++++++++-------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 0adbf96a..1661f42c 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,10 +1,10 @@ { "preparing_files": "Compression des fichiers.", - "give_this_url": "Donnez cette adresse au destinataire :", + "give_this_url": "Donnez cette adresse au destinataire :", "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", - "not_a_file": "{0:s} n'est pas un fichier valide.", - "other_page_loaded": "Adresse chargée", - "closing_automatically": "Arrêté car le transfert est fini", + "not_a_file": "{0:s} n’est pas un fichier valide.", + "other_page_loaded": "L’adresse a été chargée", + "closing_automatically": "Arrêté, car le transfert est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare démarré", "systray_download_started_message": "Une personne télécharge vos fichiers", @@ -12,14 +12,14 @@ "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "La personne a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", - "help_stay_open": "Continuer le partage après l'envoi des fichiers", - "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", + "help_stay_open": "Continuer le partage après l’envoi des fichiers", + "help_debug": "Journaliser les erreurs d’OnionShare sur la sortie standard et les erreurs Web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", - "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", + "gui_drag_and_drop": "Glisser-déposer des fichiers et dossiers\npour commencer le partage", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionner", - "gui_share_start_server": "Commencer à partager", + "gui_share_start_server": "Commencer le partage", "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier l'adresse", "gui_copy_hidservauth": "Copier HidServAuth", @@ -32,16 +32,16 @@ "gui_settings_autoupdate_timestamp_never": "Jamais", "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", - "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", - "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", - "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", - "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", + "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", + "give_this_url_receive": "Donnez cette adresse à l’expéditeur :", + "give_this_url_receive_stealth": "Donnez cette adresse et cette ligne HidServAuth à l'expéditeur :", + "not_a_readable_file": "{0:s} n’est pas un fichier lisible.", "timeout_download_still_running": "En attente de la fin du téléchargement", "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers", "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", - "gui_settings_close_after_first_download_option": "Arrêt du partage après l'envoi des fichiers", + "gui_settings_close_after_first_download_option": "Arrêter le partage après envoi des fichiers", "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", @@ -50,14 +50,14 @@ "gui_settings_authenticate_no_auth_option": "Pas d'authentification ou authentification par cookie", "gui_settings_authenticate_password_option": "Mot de passe", "gui_settings_password_label": "Mot de passe", - "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de bridge", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de pont", "gui_settings_button_save": "Sauvegarder", "gui_settings_button_cancel": "Annuler", "gui_settings_button_help": "Aide", - "gui_settings_shutdown_timeout": "Arrêter le partage à :", + "gui_settings_shutdown_timeout": "Arrêter le partage à :", "connecting_to_tor": "Connexion au réseau Tor", - "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)", - "large_filesize": "Avertissement : envoyer un gros fichier peut prendre plusieurs heures", + "help_config": "Emplacement du fichier personnalisé de configuration JSON (facultatif)", + "large_filesize": "Avertissement : envoyer un gros partage peut prendre des heures", "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p%", @@ -83,10 +83,10 @@ "gui_settings_connection_type_test_button": "Tester la connexion à Tor", "gui_settings_control_port_label": "Port de contrôle", "gui_settings_authenticate_label": "Paramètres d'authentification de Tor", - "gui_settings_tor_bridges": "Support des bridges Tor", - "gui_settings_tor_bridges_custom_radio_option": "Utiliser des bridges personnalisés", - "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des bridges à l'adresse https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Aucun des bridges que vous avez ajouté ne fonctionne.\nVérifiez les à nouveau ou ajoutez-en d'autres.", + "gui_settings_tor_bridges": "Prise en charge des ponts de Tor", + "gui_settings_tor_bridges_custom_radio_option": "Utiliser des ponts personnalisés", + "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des ponts sur https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Aucun des ponts que vous avez ajoutés ne fonctionne.\nVérifiez-les de nouveau ou ajoutez-en d’autres.", "settings_error_unknown": "Impossible de se connecter au contrôleur Tor car les paramètres n'ont pas de sens.", "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Est-ce que le navigateur Tor (disponible sur torproject.org) fonctionne en arrière-plan ?", "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", @@ -108,11 +108,11 @@ "share_via_onionshare": "Partager via OnionShare", "gui_save_private_key_checkbox": "Utiliser une adresse persistante", "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", - "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", - "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", - "gui_url_label_stay_open": "Ce partage ne s'arrêtera pas automatiquement.", - "gui_url_label_onetime": "Ce partage s'arrêtera après le premier téléchargement complété.", - "gui_url_label_onetime_and_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants devraient réutiliser l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_receive_url_description": "Quiconque possède cette adresse OnionShare peut téléverser des fichiers vers votre ordinateur en utilisant le Navigateur Tor : ", + "gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", + "gui_url_label_stay_open": "Ce partage ne s’arrêtera pas automatiquement.", + "gui_url_label_onetime": "Ce partage s’arrêtera une fois que le premier téléchargement sera terminé.", + "gui_url_label_onetime_and_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", "gui_status_indicator_share_stopped": "Prêt à partager", "gui_status_indicator_share_working": "Démarrage…", "gui_status_indicator_share_started": "Partage", @@ -124,8 +124,8 @@ "history_in_progress_tooltip": "{} en cours", "history_completed_tooltip": "{} terminé", "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", - "receive_mode_warning": "Avertissement : le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", - "gui_receive_mode_warning": "Le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_warning": "Avertissement : le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur. Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur.

    Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", "receive_mode_received_file": "Reçu : {}", "gui_mode_share_button": "Fichiers partagés", "gui_mode_receive_button": "Fichiers reçus", @@ -144,7 +144,7 @@ "gui_download_in_progress": "Téléchargement démarré {}", "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", "gui_settings_language_label": "Langue préférée", - "help_stealth": "Utilisation de l'autorisation client (avancé)", + "help_stealth": "Utilisation de l’autorisation client (avancé)", "help_receive": "Recevoir des partages au lieu de les envoyer", "gui_receive_start_server": "Démarrer le mode réception", "gui_receive_stop_server": "Arrêter le mode réception", @@ -152,7 +152,7 @@ "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", - "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", + "error_rate_limit": "Quelqu’un a effectué trop de tentatives échouées sur votre adresse, ce qui signifie que cette personne pourrait essayer de la deviner. C’est pourquoi OnionShare a arrêté le serveur. Redémarrez le partage et envoyez au destinataire une nouvelle adresse pour partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", "gui_settings_stealth_option": "Utiliser l'autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", @@ -167,33 +167,33 @@ "info_in_progress_uploads_tooltip": "{} envoi(s) en cours", "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)", "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", - "receive_mode_upload_starting": "Un envoi d'une taille totale de {} a commencé", + "receive_mode_upload_starting": "Un téléversement d’une taille totale de {} commence", "systray_close_server_message": "Une personne a arrêté le serveur", "systray_page_loaded_title": "Page chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", - "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", - "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}", + "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}", "gui_settings_tor_bridges_obfs4_radio_option": "Utiliser les transports enfichables obfs4 intégrés", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (nécessitent obfs4proxy)", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (exige obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (nécessitent obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Avertissement : les bridges meek_lite sont très coûteux à faire fonctionner pour le Tor Project.

    Utilisez les seulement si vous ne pouvez pas vous connecter à Tor directement, via les transports obfs4 ou avec d'autres bridges normaux.", - "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", - "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", - "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré", - "gui_add_files": "Ajouter fichiers", - "gui_add_folder": "Ajouter dossier", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (exige obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

    Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor, par les transports obfs4 ou autres ponts normaux.", + "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d’arrêt automatique", + "gui_server_started_after_timeout": "La minuterie d’arrêt automatique est arrivée au bout de son délai avant le démarrage du serveur.\nVeuillez mettre en place un nouveau partage.", + "gui_server_timeout_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai.\nVeuillez la mettre à jour pour commencer le partage.", + "close_on_timeout": "Arrêté, car la minuterie d’arrêt automatique est arrivée au bout de son délai", + "gui_add_files": "Ajouter des fichiers", + "gui_add_folder": "Ajouter un dossier", "error_cannot_create_data_dir": "Impossible de créer le dossier de données OnionShare : {}", "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", "gui_settings_data_dir_label": "Enregistrer les fichiers dans", "gui_settings_data_dir_browse_button": "Parcourir", "systray_page_loaded_message": "Adresse OnionShare chargée", - "systray_share_started_title": "Partage commencé", + "systray_share_started_title": "Le partage a commencé", "systray_share_started_message": "Démarrer l'envoi de fichiers à une personne", - "systray_share_completed_title": "Partage terminé", - "systray_share_canceled_title": "Partage annulé", + "systray_share_completed_title": "Le partage est terminé", + "systray_share_canceled_title": "Le partage a été annulé", "systray_share_canceled_message": "Une personne a annulé la réception de vos fichiers", "systray_receive_started_title": "Réception commencée", "systray_receive_started_message": "Une personne vous envoie des fichiers", -- cgit v1.2.3-54-g00ecf From b7791fb2400201081c32e0cd58923d428bc6a5fc Mon Sep 17 00:00:00 2001 From: michalis Date: Tue, 22 Jan 2019 09:42:46 +0000 Subject: Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ --- share/locale/el.json | 154 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 63 deletions(-) diff --git a/share/locale/el.json b/share/locale/el.json index dcdc97f1..c217716b 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -24,13 +24,13 @@ "systray_upload_started_title": "Η λήψη του OnionShare ξεκίνησε", "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου", "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", - "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά τη πρώτη λήψη", + "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά την αποστολή των αρχείων", "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", - "help_stealth": "Χρησιμοποιήστε άδεια χρήστη (Για προχωρημένους)", - "help_receive": "", - "help_debug": "", + "help_stealth": "Κάντε χρήση εξουσιοδότησης πελάτη (Για προχωρημένους)", + "help_receive": "Λάβετε διαμοιρασμένα αρχεία αντι να τα στέλνετε", + "help_debug": "Κατέγραψε τα σφάλματα του OnionShare στο stdout (συνήθως οθόνη) και τα σφάλματα web στον δίσκο", "help_filename": "Λίστα αρχείων ή φακέλων για μοίρασμα", - "help_config": "", + "help_config": "Ορίστε σημείο αποθήκευσης αρχείου JSON", "gui_drag_and_drop": "Σύρτε και αφήστε αρχεία και φακέλους\nγια να αρχίσετε να τα μοιράζεστε", "gui_add": "Προσθήκη", "gui_delete": "Διαγραφή", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", "gui_settings_window_title": "Ρυθμίσεις", "gui_settings_whats_this": " Τί είναι αυτό? ", - "gui_settings_stealth_option": "Χρήση άδειας χρήστη (αδειοδότηση)", + "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη", "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα \nνα επιλέξετε την αντιγραφή του HidServAuth σας.", "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Έλεγχος για νέα έκδοση", "gui_settings_general_label": "Γενικές ρυθμίσεις", "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης", - "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης μετά την πρώτη λήψη", + "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης αρχείων μετά την αποστολή τους", "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?", "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare", "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser", @@ -109,67 +109,67 @@ "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.", "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?", "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "settings_error_socket_file": "Ανέφικτη η σύνδεση με τον ελεγκτή Tor, κάνοντας χρήση αρχείου socket {}.", + "settings_error_auth": "Εγινε σύνδεση με {}:{}, αλλα δεν μπορεί να γίνει πιστοποίηση. Ισως δεν ειναι ενας ελεγκτής Tor?", + "settings_error_missing_password": "Εγινε σύνδεση με ελεγκτή Tor, αλλά απαιτείται κωδικός για πιστοποίηση.", + "settings_error_unreadable_cookie_file": "Εγινε σύνδεση με ελεγκτή Tor, αλλα ο κωδικός πιθανόν να είναι λάθος ή ο χρήστης δεν επιτρέπεται να διαβάζει αρχεία cookie.", + "settings_error_bundled_tor_not_supported": "Η έκδοση Tor που συνοδεύει το OnionShare δεν λειτουργεί σε περιβάλλον προγραμματιστή σε Windows ή macOS.", + "settings_error_bundled_tor_timeout": "Η σύνδεση με Tor αργεί αρκετά. Ισως δεν είστε συνδεδεμένοι στο Διαδίκτυο ή το ρολόι σας δεν ειναι συγχρονισμένο?", + "settings_error_bundled_tor_broken": "Το OnionShare δεν μπορεί να συνδεθεί με το Tor στο παρασκήνιο:\n{}", + "settings_test_success": "Εγινε σύνδεση με τον ελεγκτή Tor.\n\nΕκδοση Tor: {}\nΥποστηρίζει εφήμερες υπηρεσίες onion: {}.\nΥποστηρίζει πιστοποίηση πελάτη: {}.\nΥποστηρίζει νέας γενιάς διευθύνσεις .onion: {}.", + "error_tor_protocol_error": "Υπήρξε σφάλμα με το Tor: {}", + "error_tor_protocol_error_unknown": "Υπήρξε άγνωστο σφάλμα με το Tor", + "error_invalid_private_key": "Αυτο το ιδιωτικό κλειδί δεν υποστηρίζεται", + "connecting_to_tor": "Γίνεται σύνδεση με το δίκτυο Tor", + "update_available": "Βγήκε ενα νέο OnionShare. Κάντε κλικ εδώ για να το λάβετε.

    Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.", + "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση δεν αναγνωρίζεται '{}'…", + "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένοι στο Tor ή ο ιστότοπος OnionShare είναι κάτω?", + "update_not_available": "Εχετε την πιό πρόσφατη έκδοση OnionShare.", + "gui_tor_connection_ask": "Να ανοίξετε τις ρυθμίσεις για να επιλύσετε την σύνδεση με το Tor?", + "gui_tor_connection_ask_open_settings": "Ναι", + "gui_tor_connection_ask_quit": "Εξοδος", + "gui_tor_connection_error_settings": "Προσπαθήστε να αλλάξετε τον τρόπο σύνδεσης του OnionShare, με το δίκτυο Tor, από τις ρυθμίσεις.", + "gui_tor_connection_canceled": "Δεν μπόρεσε να γίνει σύνδεση με Tor.\n\nΕλέγξτε ότι είστε συνδεδεμένοι στο Διαδίκτυο, επανεκινήστε το OnionShare και ρυθμίστε την σύνδεση με το Tor.", + "gui_tor_connection_lost": "Εγινε αποσύνδεση απο το Tor.", + "gui_server_started_after_timeout": "Η λειτουργία auto-stop τερματίστηκε πριν την εκκίνηση διακομιστή.\nΠαρακαλώ κάντε εναν νέο διαμοιρασμό.", + "gui_server_timeout_expired": "Η λειτουργία auto-stop ήδη τερματίστηκε.\nΕνημερώστε την για να ξεκινήσετε τον διαμοιρασμό.", + "share_via_onionshare": "Κάντε το OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Χρηση \"παραδοσιακών\" διευθύνσεων", + "gui_save_private_key_checkbox": "Χρήση μόνιμης διεύθυνσης", + "gui_share_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να κατεβάσει τα αρχεία σας με χρήση Φυλλομετρητη Tor: ", + "gui_receive_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να ανεβάσει αρχεία στον υπολογιστή σας με χρήση του Φυλλομετρητή Tor: ", + "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

    Οποιοσδήποτε μετέπειτα διαμοιρασμός κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_url_label_stay_open": "Αυτος ο διαμοιρασμός δεν έχει auto-stop.", + "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει με την πρώτη λήψη.", + "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

    Οποιοσδήποτε μετέπειτα διαμοιρασμός θα κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_status_indicator_share_stopped": "Ετοιμο για διαμοιρασμό", + "gui_status_indicator_share_working": "Ξεκινάει…", + "gui_status_indicator_share_started": "Διαμοιράζει", + "gui_status_indicator_receive_stopped": "Ετοιμο για λήψη", + "gui_status_indicator_receive_working": "Ξεκινάει…", + "gui_status_indicator_receive_started": "Γίνεται λήψη", + "gui_file_info": "{} αρχεία, {}", + "gui_file_info_single": "{} αρχείο, {}", + "history_in_progress_tooltip": "{} σε εξέλιξη", + "history_completed_tooltip": "{} ολοκληρώθηκε", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "Προσοχή: η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας. Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", + "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας.

    Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", + "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει", + "receive_mode_received_file": "Ελήφθη: {}", + "gui_mode_share_button": "Διαμοίρασε αρχεία", + "gui_mode_receive_button": "Λήψη αρχείων", + "gui_settings_receiving_label": "Ρυθμίσεις λήψης", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "Δημόσια λειτουργία", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "Η σελίδα φορτώθηκε", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,8 +179,36 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "", - "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος" + "gui_open_folder_error_nautilus": "Δεν μπορεί να ανοιχτεί ο φάκελος γιατί το nautilus δεν είναι διαθέσιμο. Το αρχείο είναι εδω: {}", + "gui_settings_language_label": "Προτιμώμενη γλώσσα", + "gui_settings_language_changed_notice": "Επανεκινήστε το OnionShare για να ενεργοποιηθεί η αλλαγή γλώσσας.", + "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος", + "gui_add_files": "Προσθέστε αρχεία", + "gui_add_folder": "Προσθέστε φάκελο", + "gui_connect_to_tor_for_onion_settings": "Συνδεθείτε με Tor για να δείτε τις ρυθμίσεις της υπηρεσίας onion", + "error_cannot_create_data_dir": "Δεν ήταν δυνατό να δημιουργηθεί φάκελος δεδομένων OnionShare: {}", + "receive_mode_data_dir": "Τα αρχεία που στάλθηκαν σε εσας εμφανίζοντε στον φάκελο: {}", + "gui_settings_data_dir_label": "Αποθήκευσε αρχεία στο", + "gui_settings_data_dir_browse_button": "Περιήγηση", + "systray_page_loaded_message": "Η διεύθυνση OnionShare φορτώθηκε", + "systray_share_started_title": "Ο διαμοιρασμός ξεκίνησε", + "systray_share_started_message": "Ξεκίνησε η αποστολή αρχείων σε κάποιον", + "systray_share_completed_title": "Ο διαμοιρασμός ολοκληρώθηκε", + "systray_share_completed_message": "Ολοκληρώθηκε η αποστολή αρχείων", + "systray_share_canceled_title": "Ο διαμοιρασμός ακυρώθηκε", + "systray_share_canceled_message": "Κάποιος ακύρωσε την λήψη των αρχείων σας", + "systray_receive_started_title": "Η λήψη ξεκίνησε", + "systray_receive_started_message": "Κάποιος σας στέλνει αρχεία", + "gui_all_modes_history": "Ιστορικό", + "gui_all_modes_clear_history": "Καθαρισμός όλων", + "gui_all_modes_transfer_started": "Ξεκινησε {}", + "gui_all_modes_transfer_finished_range": "Μεταφέρθηκαν {} - {}", + "gui_all_modes_transfer_finished": "Μεταφέρθηκαν {} - {}", + "gui_all_modes_progress_complete": "%p%, {0:s} διάρκεια.", + "gui_all_modes_progress_starting": "{0:s}, %p% (γίνεται υπολογισμός)", + "gui_all_modes_progress_eta": "{0:s}, εκτίμηση: {1:s}, %p%", + "gui_share_mode_no_files": "Δεν Στάλθηκαν Αρχεία Ακόμα", + "gui_share_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση αποστολής", + "gui_receive_mode_no_files": "Δεν Εγινε Καμμία Λήψη Αρχείων Ακόμα", + "gui_receive_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση της λήψης" } -- cgit v1.2.3-54-g00ecf From 2266158875ad36ce58bf99d7d1d0e2427f5193b5 Mon Sep 17 00:00:00 2001 From: michalis Date: Tue, 22 Jan 2019 09:47:46 +0000 Subject: Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ --- share/locale/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/locale/it.json b/share/locale/it.json index 17d4e770..a21d6c5d 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -85,7 +85,7 @@ "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", "timeout_upload_still_running": "In attesa del completamento dell'upload", - "gui_add_files": "Aggiungi file", + "gui_add_files": "Aggiungi archivi", "gui_add_folder": "Aggiungi una cartella", "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo", "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket", -- cgit v1.2.3-54-g00ecf From 08304e977e682d7a0431fdd7fbb0c7bfdd2da990 Mon Sep 17 00:00:00 2001 From: michalis Date: Tue, 22 Jan 2019 12:32:56 +0000 Subject: Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ --- share/locale/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/locale/ru.json b/share/locale/ru.json index 254b5ef8..4655a2e5 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -181,5 +181,8 @@ "gui_upload_finished": "Выгружено {}", "gui_download_in_progress": "Загрузка Началась {}", "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку nautilus не доступен. Файл находится здесь: {}", - "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки." + "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки.", + "gui_add_files": "Добавить файлы", + "gui_add_folder": "Добавить папку", + "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}" } -- cgit v1.2.3-54-g00ecf From f1a598e91962c32fa423b22b9e575858fbdbf541 Mon Sep 17 00:00:00 2001 From: Jonatan Nyberg Date: Fri, 25 Jan 2019 18:43:53 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 59 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index c0e28ec7..779b235e 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -3,15 +3,15 @@ "preparing_files": "Komprimera filer.", "give_this_url": "Ge den här adressen till mottagaren:", "give_this_url_stealth": "Ge den här adressen och HidServAuth-raden till mottagaren:", - "give_this_url_receive": "Ge den här adressen till avsändaren:", - "give_this_url_receive_stealth": "Ge den här adressen och HidServAuth-raden till avsändaren:", + "give_this_url_receive": "Ge denna adress till avsändaren:", + "give_this_url_receive_stealth": "Ge denna adress och HidServAuth till avsändaren:", "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern", "not_a_file": "{0:s} är inte en giltig fil.", "not_a_readable_file": "{0:s} är inte en läsbar fil.", - "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", + "no_available_port": "Kunde inte hitta en ledig kort för att börja onion-tjänsten", "other_page_loaded": "Adress laddad", "close_on_timeout": "Stoppad för att automatiska stopp-timern tiden tog slut", - "closing_automatically": "Stannade för att nedladdningen blev klar", + "closing_automatically": "Stoppad för att hämtningen är klar", "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", "large_filesize": "Varning: Att skicka en stor fil kan ta timmar", @@ -25,22 +25,22 @@ "systray_upload_started_title": "OnionShare Uppladdning Påbörjad", "systray_upload_started_message": "En användare började ladda upp filer på din dator", "help_local_only": "Använd inte Tor (endast för utveckling)", - "help_stay_open": "Fortsätt dela efter första nedladdning", + "help_stay_open": "Fortsätt dela efter att filer har skickats", "help_shutdown_timeout": "Avbryt delning efter ett bestämt antal sekunder", "help_stealth": "Använd klient-auktorisering (avancerat)", "help_receive": "Ta emot delningar istället för att skicka dem", "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", "help_filename": "Lista filer och mappar att dela", "help_config": "Egenvald sökväg för JSON konfigurationsfil (valfri)", - "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att påbörja delning", + "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att börja delning", "gui_add": "Lägg till", "gui_delete": "Radera", "gui_choose_items": "Välj", - "gui_share_start_server": "Påbörja delning", + "gui_share_start_server": "Börja dela", "gui_share_stop_server": "Avbryt delning", "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern slutar vid {}", - "gui_receive_start_server": "Starta Mottagarläge", + "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern avslutar vid {}", + "gui_receive_start_server": "Börja mottagarläge", "gui_receive_stop_server": "Avsluta Mottagarläge", "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer avslutas kl {}", @@ -49,8 +49,8 @@ "gui_downloads": "Nedladdningshistorik", "gui_no_downloads": "Inga Nedladdningar Än", "gui_canceled": "Avbruten", - "gui_copied_url_title": "OnionShare Adress Kopierad", - "gui_copied_url": "OnionShare adress kopierad till urklipp", + "gui_copied_url_title": "OnionShare-adress kopierad", + "gui_copied_url": "OnionShare-adress kopierad till urklipp", "gui_copied_hidservauth_title": "HidServAuth Kopierad", "gui_copied_hidservauth": "HidServAuth-rad kopierad till urklipp", "gui_please_wait": "Börjar... klicka för att avbryta.", @@ -78,9 +78,9 @@ "gui_settings_autoupdate_check_button": "Sök efter ny version", "gui_settings_general_label": "Allmänna inställningar", "gui_settings_sharing_label": "Delningsinställningar", - "gui_settings_close_after_first_download_option": "Sluta dela efter första hämtningen", + "gui_settings_close_after_first_download_option": "Fortsätt dela efter att filer har skickats", "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", - "gui_settings_connection_type_bundled_option": "Använd Tor-versionen inbyggd i OnionShare", + "gui_settings_connection_type_bundled_option": "Använd Tor-versionen som är inbyggd i OnionShare", "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Anslut med kontrollport", "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", @@ -122,9 +122,9 @@ "error_tor_protocol_error_unknown": "Det fanns ett okänt fel med Tor", "error_invalid_private_key": "Denna privata nyckeltyp stöds inte", "connecting_to_tor": "Ansluter till Tor-nätverket", - "update_available": "Ny OnionShare utgiven. Klicka här för att få det.

    Du använder {} och det senaste är {}.", + "update_available": "Ny OnionShare utgiven. Klicka här för att få den.

    Du använder {} och den senaste är {}.", "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", - "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: kanske är du inte ansluten till Tor, eller OnionShare-webbplatsen är nere?", + "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: Kanske är du inte ansluten till Tor, eller är OnionShare-webbplatsen nere?", "update_not_available": "Du kör den senaste OnionShare.", "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?", "gui_tor_connection_ask_open_settings": "Ja", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "Offentligt läge", "systray_close_server_title": "OnionShare-servern stängd", "systray_close_server_message": "En användare stängde servern", - "systray_page_loaded_title": "OnionShare-sidan lästes in", + "systray_page_loaded_title": "Sidan lästes in", "systray_download_page_loaded_message": "En användare läste in hämtningssidan", "systray_upload_page_loaded_message": "En användare läste in sändningssidan", "gui_uploads": "Sändningshistoriken", @@ -184,5 +184,30 @@ "gui_settings_language_changed_notice": "Starta om OnionShare för att din språkändring ska träda i kraft.", "gui_add_files": "Lägg till filer", "gui_add_folder": "Lägg till mapp", - "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar" + "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar", + "error_cannot_create_data_dir": "Det gick inte att skapa OnionShare-datamapp: {}", + "receive_mode_data_dir": "Filer som skickas till dig visas i den här mappen: {}", + "gui_settings_data_dir_label": "Spara filer till", + "gui_settings_data_dir_browse_button": "Bläddra", + "systray_page_loaded_message": "OnionShare-adress lästes in", + "systray_share_started_title": "Delning börjades", + "systray_share_started_message": "Börjar skicka filer till någon", + "systray_share_completed_title": "Delning klar", + "systray_share_completed_message": "Filerna skickades", + "systray_share_canceled_title": "Delning avbruten", + "systray_share_canceled_message": "Någon har avbrutit att ta emot dina filer", + "systray_receive_started_title": "Mottagning startad", + "systray_receive_started_message": "Någon skickar filer till dig", + "gui_all_modes_history": "Historik", + "gui_all_modes_clear_history": "Rensa alla", + "gui_all_modes_transfer_started": "Började {}", + "gui_all_modes_transfer_finished_range": "Överförd {} - {}", + "gui_all_modes_transfer_finished": "Överförd {}", + "gui_all_modes_progress_complete": "%p%, {0} förflutit.", + "gui_all_modes_progress_starting": "{0} %s% (beräkning)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Inga filer har skickats än", + "gui_share_mode_timeout_waiting": "Väntar på att avsluta sändningen", + "gui_receive_mode_no_files": "Inga filer har mottagits ännu", + "gui_receive_mode_timeout_waiting": "Väntar på att avsluta mottagande" } -- cgit v1.2.3-54-g00ecf From c336065f9c899201bde9e0860299fda40e3dd07d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 25 Jan 2019 14:16:58 -0800 Subject: Version bump to 2.0.dev3 --- install/onionshare.nsi | 2 +- share/version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/install/onionshare.nsi b/install/onionshare.nsi index 3a4c6c2a..d29e10a5 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -6,7 +6,7 @@ !define INSTALLSIZE 115186 !define VERSIONMAJOR 2 !define VERSIONMINOR 0 -!define VERSIONSTRING "2.0" +!define VERSIONSTRING "2.0.dev3" RequestExecutionLevel admin diff --git a/share/version.txt b/share/version.txt index 8be5e08a..c0099a45 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.0.dev2 +2.0.dev3 -- cgit v1.2.3-54-g00ecf From 612f3d530b136a7062dade04c5c6ffa1217c703c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 25 Jan 2019 14:21:09 -0800 Subject: Update build instructions to use Python 3.7 and Qt 5.11.3 --- BUILD.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/BUILD.md b/BUILD.md index 6073d1a9..e3d6a760 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,11 +46,11 @@ If you find that these instructions don't work for your Linux distribution or ve Install Xcode from the Mac App Store. Once it's installed, run it for the first time to set it up. Also, run this to make sure command line tools are installed: `xcode-select --install`. And finally, open Xcode, go to Preferences > Locations, and make sure under Command Line Tools you select an installed version from the dropdown. (This is required for installing Qt5.) -Download and install Python 3.7.0 from https://www.python.org/downloads/release/python-370/. I downloaded `python-3.7.0-macosx10.9.pkg`. +Download and install Python 3.7.2 from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2-macosx10.9.pkg`. You may also need to run the command `/Applications/Python\ 3.7/Install\ Certificates.command` to update Python 3.6's internal certificate store. Otherwise, you may find that fetching the Tor Browser .dmg file fails later due to a certificate validation error. -Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.5-online.dmg`. There's no need to login to a Qt account during installation. When you select components, install the `macOS` component from Qt 5.11.1 (or whatever the latest Qt version is). +Install Qt 5.11.3 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-mac-x64-3.0.6-online.dmg`. In the installer, you can skip making an account, and all you need is `Qt` > `Qt 5.11.3` > `macOS`. Now install some python dependencies with pip (note, there's issues building a .app if you install this in a virtualenv): @@ -85,7 +85,7 @@ Now you should have `dist/OnionShare.pkg`. ### Setting up your dev environment -Download Python 3.7.0, 32-bit (x86) from https://www.python.org/downloads/release/python-370/. I downloaded `python-3.7.0.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer. +Download Python 3.7.2, 32-bit (x86) from https://www.python.org/downloads/release/python-372/. I downloaded `python-3.7.2.exe`. When installing it, make sure to check the "Add Python 3.7 to PATH" checkbox on the first page of the installer. Open a command prompt, cd to the onionshare folder, and install dependencies with pip: @@ -93,7 +93,7 @@ Open a command prompt, cd to the onionshare folder, and install dependencies wit pip install -r install\requirements.txt ``` -Download and install Qt5 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.5-online.exe`. There's no need to login to a Qt account during installation. When you can select components, install the `MSVC 2015 32-bit` component from Qt 5.11.1 (or whatever the latest Qt version is). +Install the Qt 5.11.3 from https://www.qt.io/download-open-source/. I downloaded `qt-unified-windows-x86-3.0.6-online.exe`. In the installer, you can skip making an account, and all you need `Qt` > `Qt 5.11.3` > `MSVC 2015 32-bit`. After that you can try both the CLI and the GUI version of OnionShare: @@ -114,14 +114,14 @@ Download and install the standalone [Windows 10 SDK](https://dev.windows.com/en- Add the following directories to the path: -* `C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x86` -* `C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86` -* `C:\Users\user\AppData\Local\Programs\Python\Python36-32\Lib\site-packages\PyQt5\Qt\bin` +* `C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86` +* `C:\Program Files (x86)\Windows Kits\10\Redist\10.0.17763.0\ucrt\DLLs\x86` +* `C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyQt5\Qt\bin` * `C:\Program Files (x86)\7-Zip` If you want to build the installer: -* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.03-setup.exe`. +* Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.04-setup.exe`. * Add `C:\Program Files (x86)\NSIS` to the path. If you want to sign binaries with Authenticode: -- cgit v1.2.3-54-g00ecf From 195d3492d821e555178058ec36007d82276c7d07 Mon Sep 17 00:00:00 2001 From: Allan Nordhøy Date: Fri, 25 Jan 2019 22:22:18 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- share/locale/no.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/share/locale/no.json b/share/locale/no.json index 6d5a41f0..9d67e6fa 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -25,7 +25,7 @@ "systray_upload_started_title": "OnionShare-opplasting startet", "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin", "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)", - "help_stay_open": "Fortsett å dele etter første nedlasting", + "help_stay_open": "Fortsett å dele etter at filene har blitt sendt", "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder", "help_stealth": "Bruk klientidentifisering (avansert)", "help_receive": "Motta delinger istedenfor å sende dem", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", "gui_settings_window_title": "Innstillinger", "gui_settings_whats_this": "Hva er dette?", - "gui_settings_stealth_option": "Bruk klientidentifisering (gammeldags)", + "gui_settings_stealth_option": "Bruk klientidentifisering", "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.", "gui_settings_autoupdate_label": "Se etter ny versjon", "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Se etter ny versjon", "gui_settings_general_label": "Hovedinnstillinger", "gui_settings_sharing_label": "Delingsinnstillinger", - "gui_settings_close_after_first_download_option": "Stopp deling etter første nedlasting", + "gui_settings_close_after_first_download_option": "Stopp deling etter at filene har blitt sendt", "gui_settings_connection_type_label": "Hvordan skal OnionShare koble seg til Tor?", "gui_settings_connection_type_bundled_option": "Bruk Tor-versjonen som kommer med OnionShare", "gui_settings_connection_type_automatic_option": "Forsøk automatisk oppsett med Tor-nettleser", @@ -136,7 +136,7 @@ "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", "share_via_onionshare": "OnionShare det", "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", - "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (gammeldags)", + "gui_save_private_key_checkbox": "Bruk en vedvarende adresse", "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-nettleseren: ", "gui_receive_url_description": "Alle som har denne OnionShare-adressen kan Laste opp filer til din datamaskin ved bruk av Tor-nettleseren: ", "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", @@ -171,7 +171,7 @@ "gui_settings_public_mode_checkbox": "Offentlig modus", "systray_close_server_title": "OnionShare-tjener lukket", "systray_close_server_message": "En bruker stengte tjeneren", - "systray_page_loaded_title": "OnionShare-side lastet", + "systray_page_loaded_title": "Side innlastet", "systray_download_page_loaded_message": "En bruker lastet inn nedlastingssiden", "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden", "gui_uploads": "Opplastingshistorikk", @@ -214,5 +214,7 @@ "gui_share_mode_no_files": "Ingen filer sendt enda", "gui_share_mode_timeout_waiting": "Venter på fullføring av forsendelse", "gui_receive_mode_no_files": "Ingen filer mottatt enda", - "gui_receive_mode_timeout_waiting": "Venter på fullføring av mottak" + "gui_receive_mode_timeout_waiting": "Venter på fullføring av mottak", + "gui_all_modes_transfer_canceled_range": "Avbrutt {} - {}", + "gui_all_modes_transfer_canceled": "Avbrutt {}" } -- cgit v1.2.3-54-g00ecf From b85c75b79b508a6b89ab1848267aeb2a31d6fef0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 26 Jan 2019 23:01:13 -0800 Subject: Add instructions to build PyInstaller from source, to avoid AV false positives --- BUILD.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/BUILD.md b/BUILD.md index e3d6a760..34435633 100644 --- a/BUILD.md +++ b/BUILD.md @@ -58,14 +58,14 @@ Now install some python dependencies with pip (note, there's issues building a . pip3 install -r install/requirements.txt ``` -You can run both the CLI and GUI versions of OnionShare without building an bundle: +#### You can run both the CLI and GUI versions of OnionShare without building an bundle ```sh ./dev_scripts/onionshare ./dev_scripts/onionshare-gui ``` -To build the app bundle: +#### To build the app bundle ```sh install/build_osx.sh @@ -73,7 +73,7 @@ install/build_osx.sh Now you should have `dist/OnionShare.app`. -To codesign and build a pkg for distribution: +#### To codesign and build a pkg for distribution ```sh install/build_osx.sh --release @@ -102,7 +102,7 @@ python dev_scripts\onionshare python dev_scripts\onionshare-gui ``` -If you want to build a .exe: +#### If you want to build a .exe These instructions include adding folders to the path in Windows. To do this, go to Start and type "advanced system settings", and open "View advanced system settings" in the Control Panel. Click Environment Variables. Under "System variables" double-click on Path. From there you can add and remove folders that are available in the PATH. @@ -119,12 +119,78 @@ Add the following directories to the path: * `C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyQt5\Qt\bin` * `C:\Program Files (x86)\7-Zip` -If you want to build the installer: +#### If you want the .exe to not get falsely flagged as malicious by anti-virus software + +OnionShare uses PyInstaller to turn the python source code into Windows executable `.exe` file. Apparently, malware developers also use PyInstaller, and some anti-virus vendors have included snippets of PyInstaller code in their virus definitions. To avoid this, you have to compile the Windows PyInstaller bootloader yourself instead of using the pre-compiled one that comes with PyInstaller. Here's how: + +Download and install [Microsoft Build Tools for Visual Studio 2017](https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017). I downloaded `vs_buildtools.exe`. In the installer, check the box next to "Visual C++ build tools". Click "Individual components", and under "Compilers, build tools and runtimes", check "Windows Universal CRT SDK". Then click install. When installation is done, you may have to reboot your computer. + +Then, enable the 32-bit Visual C++ Toolset on the Command Line like this: + +``` +cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build" +vcvars32.bat +``` + +Make sure you have a new enough `setuptools`: + +``` +pip install setuptools==40.6.3 +``` + +Now uninstall PyInstaller from pip: + +``` +pip uninstall PyInstaller +rmdir C:\Users\user\AppData\Local\Programs\Python\Python37-32\Lib\site-packages\PyInstaller /S +``` + +Change to a folder where you keep source code, and clone the PyInstaller git repo: + +``` +git clone https://github.com/pyinstaller/pyinstaller.git +``` + +To verify the git tag, you first need the signing key's PGP key, which means you need `gpg`. If you installed git from git-scm.com, you can run this from Git Bash: + +``` +gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65 +``` + +And now verify the tag: + +``` +cd pyinstaller +git tag -v v3.4 +``` + +It should say `Good signature from "Hartmut Goebel `. If it verified successfully, checkout the tag: + +``` +git checkout v3.4 +``` + +And compile the bootloader, following [these instructions](https://pythonhosted.org/PyInstaller/bootloader-building.html). To compile, run this: + +``` +cd bootloader +python waf distclean all --target-arch=32bit --msvc_targets=x86 +``` + +Finally, install the PyInstaller module into your local site-packages: + +``` +pythin setup.py install +``` + +Now the next time you use PyInstaller to build OnionShare, the `.exe` file should not be flagged as malicious by anti-virus. + +#### If you want to build the installer * Go to http://nsis.sourceforge.net/Download and download the latest NSIS. I downloaded `nsis-3.04-setup.exe`. * Add `C:\Program Files (x86)\NSIS` to the path. -If you want to sign binaries with Authenticode: +#### If you want to sign binaries with Authenticode * You'll need a code signing certificate. I got an open source code signing certificate from [Certum](https://www.certum.eu/certum/cert,offer_en_open_source_cs.xml). * Once you get a code signing key and certificate and covert it to a pfx file, import it into your certificate store. -- cgit v1.2.3-54-g00ecf From 6ed63143df1aeb3aedd06a1f459f49437993816b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 26 Jan 2019 23:06:21 -0800 Subject: Remove PyInstaller from requirements.txt, because it now breaks CircleCI --- BUILD.md | 9 +++++++-- install/requirements.txt | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/BUILD.md b/BUILD.md index 34435633..b01faaf1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -56,6 +56,7 @@ Now install some python dependencies with pip (note, there's issues building a . ```sh pip3 install -r install/requirements.txt +pip3 install PyInstaller==3.4 ``` #### You can run both the CLI and GUI versions of OnionShare without building an bundle @@ -121,7 +122,11 @@ Add the following directories to the path: #### If you want the .exe to not get falsely flagged as malicious by anti-virus software -OnionShare uses PyInstaller to turn the python source code into Windows executable `.exe` file. Apparently, malware developers also use PyInstaller, and some anti-virus vendors have included snippets of PyInstaller code in their virus definitions. To avoid this, you have to compile the Windows PyInstaller bootloader yourself instead of using the pre-compiled one that comes with PyInstaller. Here's how: +OnionShare uses PyInstaller to turn the python source code into Windows executable `.exe` file. Apparently, malware developers also use PyInstaller, and some anti-virus vendors have included snippets of PyInstaller code in their virus definitions. To avoid this, you have to compile the Windows PyInstaller bootloader yourself instead of using the pre-compiled one that comes with PyInstaller. + +(If you don't care about this, you can install PyInstaller with `pip install PyInstaller==3.4`.) + +Here's how to compile the PyInstaller bootloader: Download and install [Microsoft Build Tools for Visual Studio 2017](https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017). I downloaded `vs_buildtools.exe`. In the installer, check the box next to "Visual C++ build tools". Click "Individual components", and under "Compilers, build tools and runtimes", check "Windows Universal CRT SDK". Then click install. When installation is done, you may have to reboot your computer. @@ -138,7 +143,7 @@ Make sure you have a new enough `setuptools`: pip install setuptools==40.6.3 ``` -Now uninstall PyInstaller from pip: +Now make sure you don't have PyInstaller installed from pip: ``` pip uninstall PyInstaller diff --git a/install/requirements.txt b/install/requirements.txt index 81430398..76dfb1ef 100644 --- a/install/requirements.txt +++ b/install/requirements.txt @@ -15,7 +15,6 @@ MarkupSafe==1.1.0 pefile==2018.8.8 pycparser==2.19 pycryptodome==3.7.2 -PyInstaller==3.4 PyQt5==5.11.3 PyQt5-sip==4.19.13 PySocks==1.6.8 -- cgit v1.2.3-54-g00ecf From 9ab75781275bb9a98c0de96ba2e3ba5ab9824558 Mon Sep 17 00:00:00 2001 From: scootergrisen Date: Sun, 27 Jan 2019 01:47:25 +0000 Subject: Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ --- share/locale/da.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/da.json b/share/locale/da.json index 48090c82..b87f0151 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -213,5 +213,7 @@ "gui_share_mode_no_files": "Der er endnu ikke sendt nogen filer", "gui_share_mode_timeout_waiting": "Venter på at blive færdig med at sende", "gui_receive_mode_no_files": "Der er endnu ikke modtaget nogen filer", - "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage" + "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage", + "gui_all_modes_transfer_canceled_range": "Annullerede {} - {}", + "gui_all_modes_transfer_canceled": "Annullerede {}" } -- cgit v1.2.3-54-g00ecf From ce81e2b798b9bd5b5506b68069a65b627cf1db3d Mon Sep 17 00:00:00 2001 From: Taro Tanaka Date: Sat, 26 Jan 2019 03:14:56 +0000 Subject: Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ja.json | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/share/locale/ja.json b/share/locale/ja.json index 77ae9b58..fae2109e 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -11,7 +11,7 @@ "no_available_port": "onionサービスを実行するための利用可能ポートを見つかりません", "other_page_loaded": "アドレスはロードされています", "close_on_timeout": "自動タイマーがタイムアウトしたため停止されました", - "closing_automatically": "ダウンロードが完了されたため停止されました", + "closing_automatically": "転送が完了されたため停止されました", "timeout_download_still_running": "ダウンロード完了待ち", "timeout_upload_still_running": "アップロード完了待ち", "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない", @@ -25,7 +25,7 @@ "systray_upload_started_title": "OnionShareアップロードは開始されました", "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました", "help_local_only": "Torを使わない(開発利用のみ)", - "help_stay_open": "最初ダウンロード後に共有し続けます", + "help_stay_open": "ファイルが送信された後に共有し続けます", "help_shutdown_timeout": "数秒後に共有が停止されます", "help_stealth": "クライアント認証を使う(上級者向け)", "help_receive": "送信の代わりに受信を優先する", @@ -80,7 +80,7 @@ "gui_settings_autoupdate_check_button": "更新をチェックする", "gui_settings_general_label": "一般的設定", "gui_settings_sharing_label": "共有設定", - "gui_settings_close_after_first_download_option": "最初のダウンロード後に停止する", + "gui_settings_close_after_first_download_option": "ファイルが送信された後に停止する", "gui_settings_connection_type_label": "OnionShareがどうやってTorと接続して欲しい?", "gui_settings_connection_type_bundled_option": "OnionShareに組み込まれるTorバージョンを使用する", "gui_settings_connection_type_automatic_option": "Torブラウザと自動設定してみる", @@ -172,7 +172,7 @@ "gui_settings_public_mode_checkbox": "公開モード", "systray_close_server_title": "OnionShareサーバーは閉鎖されました", "systray_close_server_message": "ユーザーがサーバーを閉鎖しました", - "systray_page_loaded_title": "OnionShareページはロードされました", + "systray_page_loaded_title": "ページはロードされました", "systray_download_page_loaded_message": "ユーザーがダウンロードページをロードしました", "systray_upload_page_loaded_message": "ユーザーがアップロードページをロードしました", "gui_uploads": "アップロード履歴", @@ -184,5 +184,32 @@ "gui_download_in_progress": "ダウンロード開始しました {}", "gui_open_folder_error_nautilus": "nautilusを利用できないためフォルダーを開けません。ファイルはここに保存されました: {}", "gui_settings_language_label": "優先言語", - "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。" + "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。", + "error_cannot_create_data_dir": "OnionShareのデータフォルダーを作成できませんでした: {}", + "receive_mode_data_dir": "受信されるファイルをこのフォルダーにあります: {}", + "gui_settings_data_dir_label": "ファイルの保存", + "gui_settings_data_dir_browse_button": "閲覧", + "systray_page_loaded_message": "OnionShareアドレスはロードされました", + "systray_share_started_title": "共有は始めました", + "systray_share_started_message": "誰かにファイルを通信し始めました", + "systray_share_completed_title": "共有完了", + "systray_share_completed_message": "ファイル送信完了", + "systray_share_canceled_title": "共有は停止されました", + "systray_share_canceled_message": "誰かがファイル受信を停止しました", + "systray_receive_started_title": "受信は始めました", + "systray_receive_started_message": "誰かがファイルを送信しています", + "gui_all_modes_history": "歴史", + "gui_all_modes_clear_history": "すべてクリア", + "gui_all_modes_transfer_started": "始めました {}", + "gui_all_modes_transfer_finished_range": "転送された {} - {}", + "gui_all_modes_transfer_finished": "転送された {}", + "gui_all_modes_transfer_canceled_range": "停止された {} - {}", + "gui_all_modes_transfer_canceled": "停止された {}", + "gui_all_modes_progress_complete": "%p%, 経過時間 {0:s} 。", + "gui_all_modes_progress_starting": "{0:s}, %p% (計算中)", + "gui_all_modes_progress_eta": "{0:s}, 完了予定時刻: {1:s}, %p%", + "gui_share_mode_no_files": "送信されたファイルがまだありません", + "gui_share_mode_timeout_waiting": "送信完了を待機しています", + "gui_receive_mode_no_files": "受信されたファイルがまだありません", + "gui_receive_mode_timeout_waiting": "受信完了を待機しています" } -- cgit v1.2.3-54-g00ecf From ddbc53004b56c1da1a85c0b0721abc946bc82cbc Mon Sep 17 00:00:00 2001 From: R Date: Sat, 26 Jan 2019 23:54:12 +0000 Subject: Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ --- share/locale/fa.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/share/locale/fa.json b/share/locale/fa.json index 9c266770..eafa64c1 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -201,8 +201,8 @@ "systray_receive_started_message": "شخصی در حال ارسال فایل به شماست", "gui_all_modes_history": "تاریخچه", "gui_all_modes_clear_history": "پاکسازی همه", - "gui_all_modes_transfer_started": "{} شروع شد", - "gui_all_modes_transfer_finished_range": "منتقل شد {} - {}", + "gui_all_modes_transfer_started": "{} آغاز شد", + "gui_all_modes_transfer_finished_range": "{} - {} منتقل شد", "gui_all_modes_transfer_finished": "{} منتقل شد", "gui_all_modes_progress_complete": "%p%، {0:s} سپری شد.", "gui_all_modes_progress_starting": "{0:s}, %p% (در حال محاسبه)", @@ -210,5 +210,7 @@ "gui_share_mode_no_files": "هیچ فایلی هنوز ارسال نشده است", "gui_share_mode_timeout_waiting": "انتظار برای به پایان رسیدن ارسال", "gui_receive_mode_no_files": "هیچ فایلی هنوز دریافت نشده است", - "gui_receive_mode_timeout_waiting": "انتظار برای به پایان رسیدن دریافت" + "gui_receive_mode_timeout_waiting": "انتظار برای به پایان رسیدن دریافت", + "gui_all_modes_transfer_canceled_range": "{} - {} لغو شد", + "gui_all_modes_transfer_canceled": "{} لغو شد" } -- cgit v1.2.3-54-g00ecf From 574ca37d489d80fb69871a48b9ce3cb516ac2122 Mon Sep 17 00:00:00 2001 From: Zuhualime Akoochimoya Date: Fri, 25 Jan 2019 23:06:21 +0000 Subject: Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ --- share/locale/es.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/es.json b/share/locale/es.json index ed73bd1e..3c6452a8 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -214,5 +214,7 @@ "gui_share_mode_no_files": "No se enviaron archivos todavía", "gui_share_mode_timeout_waiting": "Esperando a que termine el envío", "gui_receive_mode_no_files": "No se recibieron archivos todavía", - "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción" + "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción", + "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", + "gui_all_modes_transfer_canceled": "Cancelado {}" } -- cgit v1.2.3-54-g00ecf From 31145d26b6cf0d597bdac9a9931de57b470a9cf2 Mon Sep 17 00:00:00 2001 From: Jonatan Nyberg Date: Sat, 26 Jan 2019 08:15:11 +0000 Subject: Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ --- share/locale/sv.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/share/locale/sv.json b/share/locale/sv.json index 779b235e..f1072332 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -209,5 +209,7 @@ "gui_share_mode_no_files": "Inga filer har skickats än", "gui_share_mode_timeout_waiting": "Väntar på att avsluta sändningen", "gui_receive_mode_no_files": "Inga filer har mottagits ännu", - "gui_receive_mode_timeout_waiting": "Väntar på att avsluta mottagande" + "gui_receive_mode_timeout_waiting": "Väntar på att avsluta mottagande", + "gui_all_modes_transfer_canceled_range": "Avbröt {} - {}", + "gui_all_modes_transfer_canceled": "Avbröt {}" } -- cgit v1.2.3-54-g00ecf From 8a11d9405598a54a9ac8a832e4cda8706bdef439 Mon Sep 17 00:00:00 2001 From: communia Date: Sun, 27 Jan 2019 12:40:06 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index f0c839ef..fd99384e 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -3,15 +3,15 @@ "preparing_files": "Comprimindo arquivos.", "give_this_url": "Dar este endereço ao destinatário:", "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", - "give_this_url_receive": "Dar este endereço ao remetente:", - "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", + "give_this_url_receive": "Enviar este endereço à pessoa remetente:", + "give_this_url_receive_stealth": "Dar este endereço e HidServAuth à pessoa remetente:", "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", "not_a_file": "{0:s} não é um ficheiro válido.", "not_a_readable_file": "{0:s} não é um ficheiro legível.", "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", "other_page_loaded": "Endereço carregado", "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", - "closing_automatically": "Interrompido porque o download terminou", + "closing_automatically": "Interrompido após o término da transferência", "timeout_download_still_running": "Esperando que o download termine", "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", "systray_menu_exit": "Sair", @@ -24,7 +24,7 @@ "systray_upload_started_title": "OnionShare começou a carregar", "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", "help_local_only": "Não use Tor (unicamente para programação)", - "help_stay_open": "Continuar a compartilhar depois do primeiro download", + "help_stay_open": "Continuar a compartilhar após o envio de documentos", "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", "help_stealth": "Usar autorização de cliente (avançado)", "help_receive": "Receber compartilhamentos ao invés de enviá-los", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Configurações", "gui_settings_whats_this": "O que é isso?", - "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", + "gui_settings_stealth_option": "Usar autorização de cliente", "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", "gui_settings_autoupdate_label": "Procurar a nova versão", "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", @@ -131,17 +131,17 @@ "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.", "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", "gui_tor_connection_lost": "Desconectado do Tor.", - "gui_server_started_after_timeout": "O tempo se esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", - "gui_server_timeout_expired": "O temporizador já se esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", - "share_via_onionshare": "Compartilhar por meio de OnionShare", + "gui_server_started_after_timeout": "O tempo esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", + "gui_server_timeout_expired": "O temporizador já esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", + "share_via_onionshare": "Compartilhar usando OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", - "gui_save_private_key_checkbox": "Usar um endereço persistente (modo antigo)", + "gui_save_private_key_checkbox": "Usar o mesmo endereço", "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", - "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode fazer upload de arquivos para o seu computador usando o Tor Browser: ", - "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode carregar arquivos no seu computador usando o Tor Browser: ", + "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desative a opção \"Usar o mesmo endereço\" nas configurações.)", "gui_url_label_stay_open": "Este compartilhamento não sera encerrado automaticamente.", - "gui_url_label_onetime": "Este compartilhamento será encerrado depois da primeira vez que for utilizado.", - "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_url_label_onetime": "Este compartilhamento será encerrado após completar uma vez.", + "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desative a opção \"Usar o mesmo endereço\" nas configurações.)", "gui_status_indicator_share_stopped": "Pronto para compartilhar", "gui_status_indicator_share_working": "Começando…", "gui_status_indicator_share_started": "Compartilhando", @@ -149,7 +149,7 @@ "gui_status_indicator_receive_working": "Começando…", "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", - "gui_file_info_single": "{} ficheiro, {}", + "gui_file_info_single": "{} arquivo, {}", "history_in_progress_tooltip": "{} em progresso", "history_completed_tooltip": "{} completado", "info_in_progress_uploads_tooltip": "{} upload(s) em progresso", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare encerrado", "systray_close_server_message": "Um usuário encerrou o servidor", - "systray_page_loaded_title": "Página OnionShare Carregada", + "systray_page_loaded_title": "Página Carregada", "systray_download_page_loaded_message": "Um usuário carregou a página de download", "systray_upload_page_loaded_message": "Um usuário carregou a página de upload", "gui_uploads": "Histórico de Uploads", @@ -182,5 +182,8 @@ "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", "gui_settings_language_label": "Idioma preferido", "gui_settings_language_changed_notice": "Reinicie o OnionShare para que sua alteração de idioma tenha efeito.", - "timeout_upload_still_running": "Esperando o término do upload" + "timeout_upload_still_running": "Esperando o término do upload", + "gui_add_files": "Adicionar Documentos", + "gui_add_folder": "Adicionar Pasta", + "gui_share_mode_no_files": "Nenhum arquivo ainda enviado" } -- cgit v1.2.3-54-g00ecf From bfab968ba7a12c7c5d86174995902f6b19efc35f Mon Sep 17 00:00:00 2001 From: Muha Aliss Date: Mon, 28 Jan 2019 01:54:01 +0000 Subject: Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ --- share/locale/tr.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/share/locale/tr.json b/share/locale/tr.json index ae6a7058..a5f5b429 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -22,5 +22,9 @@ "gui_copied_url": "Panoya kopyalanan URL", "gui_please_wait": "Lütfen bekleyin...", "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%", - "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla." + "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla.", + "give_this_url_receive": "Bu adresi gönderene ver:", + "not_a_readable_file": "{0:s} okunabilir bir dosya değil.", + "no_available_port": "Onion servisini başlatmak için uygun bir port bulunamadı", + "close_on_timeout": "Otomatik durma zamanlayıcısının bitmesi nedeniyle durdu" } -- cgit v1.2.3-54-g00ecf From 69aa79ffd79d92b7dfb246a5b54f55b88ea45c7f Mon Sep 17 00:00:00 2001 From: shaymcmillan Date: Mon, 28 Jan 2019 21:01:39 +0000 Subject: GUI translated into Russian. --- share/locale/ru.json | 174 +++++++++++++++++++++++++-------------------------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/share/locale/ru.json b/share/locale/ru.json index 254b5ef8..4f1e0ba6 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -1,10 +1,10 @@ { - "give_this_url": "Передайте этот адрес получателю:", + "give_this_url": "Передайте получателю этот адрес:", "ctrlc_to_stop": "Нажмите Ctrl+C, чтобы остановить сервер", "not_a_file": "{0:s} недопустимый файл.", "gui_copied_url": "Ссылка OnionShare скопирована в буфер обмена", "other_page_loaded": "Адрес загружен", - "gui_copy_url": "Скопировать ссылку", + "gui_copy_url": "Копировать ссылку", "systray_menu_exit": "Выйти", "gui_add": "Добавить", "gui_delete": "Удалить", @@ -14,21 +14,21 @@ "gui_quit_warning_dont_quit": "Отмена", "gui_settings_window_title": "Настройки", "gui_settings_autoupdate_timestamp_never": "Никогда", - "gui_settings_general_label": "Общие настройки", + "gui_settings_general_label": "Общие настройки:", "gui_settings_control_port_label": "Контрольный порт", "gui_settings_authenticate_password_option": "Пароль", "gui_settings_password_label": "Пароль", "gui_settings_button_save": "Сохранить", "gui_settings_button_cancel": "Отмена", "gui_settings_button_help": "Помощь", - "gui_tor_connection_ask_open_settings": "Есть", + "gui_tor_connection_ask_open_settings": "Настройки", "gui_tor_connection_ask_quit": "Выйти", - "gui_status_indicator_share_started": "Идёт раздача", + "gui_status_indicator_share_started": "Идёт отправка", "gui_status_indicator_receive_started": "Идёт получение", - "gui_settings_downloads_label": "Сохранять файлы в", + "gui_settings_downloads_label": "Путь сохранения файлов: ", "gui_settings_downloads_button": "Выбрать", "gui_clear_history": "Очистить Все", - "gui_settings_language_label": "Предпочтительный язык", + "gui_settings_language_label": "Язык интерфейса:", "config_onion_service": "Назначем \"луковому\" сервису порт {:d}.", "preparing_files": "Сжимаем файлы.", "give_this_url_stealth": "Передайте этот адрес и строку HidServAuth получателю:", @@ -38,41 +38,41 @@ "no_available_port": "Не удалось найти доступный порт для запуска \"лукового\" сервиса", "close_on_timeout": "Время ожидания таймера истекло, сервис остановлен", "closing_automatically": "Загрузка завершена, сервис остановлен", - "timeout_download_still_running": "Ожидаем завершения загрузки", - "timeout_upload_still_running": "Ожидаем завершения выгрузки", - "large_filesize": "Внимание: Отправка раздачи большого объёма может занять продолжительное время (часы)", - "systray_download_started_title": "OnionShare: Загрузка Началась", - "systray_download_started_message": "Пользователь начал загружать ваши файлы", - "systray_download_completed_title": "OnionShare: Загрузка Завершена", - "systray_download_completed_message": "Пользователь завершил загрузку ваших файлов", - "systray_download_canceled_title": "OnionShare: Загрузка Отменена", - "systray_download_canceled_message": "Пользователь отменил загрузку", - "systray_upload_started_title": "OnionShare: Выгрузка Началась", - "systray_upload_started_message": "Пользователь начал выгрузку файлов на ваш компьютер", + "timeout_download_still_running": "Ожидаем завершения скачивания", + "timeout_upload_still_running": "Ожидаем завершения загрузки", + "large_filesize": "Внимание: Отправка данных большого объёма может занять продолжительное время (несколько часов)", + "systray_download_started_title": "OnionShare: скачивание началось", + "systray_download_started_message": "Пользователь начал загружать Ваши файлы", + "systray_download_completed_title": "OnionShare: скачивание завершено", + "systray_download_completed_message": "Пользователь завершил скачивание Ваших файлов", + "systray_download_canceled_title": "OnionShare: скачивание отменено", + "systray_download_canceled_message": "Пользователь отменил скачивание", + "systray_upload_started_title": "OnionShare: загрузка началась", + "systray_upload_started_message": "Пользователь начал загрузку файлов на Ваш компьютер", "help_local_only": "Не использовать Tor (только для разработки)", - "help_stay_open": "Продолжить раздачу после первой загрузки", - "help_shutdown_timeout": "Остановить раздачу после заданного количества секунд", + "help_stay_open": "Продолжить отправку после первого скачивания", + "help_shutdown_timeout": "Остановить отправку после заданного количества секунд", "help_stealth": "Использовать авторизацию клиента (дополнительно)", - "help_receive": "Получать раздачи, вместо их отправки", + "help_receive": "Получать загрузки, вместо их отправки:", "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск", - "help_filename": "Список файлов или папок для раздачи", + "help_filename": "Список файлов или папок для отправки", "help_config": "Расположение пользовательского конфигурационного JSON-файла (необязательно)", - "gui_drag_and_drop": "Перетащите сюда файлы и папки\nчтобы начать раздачу", - "gui_share_start_server": "Начать раздачу", - "gui_share_stop_server": "Закончить раздачу", - "gui_share_stop_server_shutdown_timeout": "Остановить раздачу ({}s осталось)", + "gui_drag_and_drop": "Перетащите сюда файлы и/или папки,\nкоторые хотите отправить.", + "gui_share_start_server": "Начать отправку", + "gui_share_stop_server": "Закончить отправку", + "gui_share_stop_server_shutdown_timeout": "Остановить отправку ({}s осталось)", "gui_share_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", - "gui_receive_start_server": "Включить Режим Получения", - "gui_receive_stop_server": "Выключить Режим Получения", + "gui_receive_start_server": "Включить режим получения", + "gui_receive_stop_server": "Выключить режим получения", "gui_receive_stop_server_shutdown_timeout": "Выключить Режим Получения ({}s осталось)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", "gui_copy_hidservauth": "Скопировать строку HidServAuth", - "gui_downloads": "История Загрузок", - "gui_no_downloads": "Пока нет загрузок", + "gui_downloads": "История скачиваний", + "gui_no_downloads": "Скачиваний пока нет ", "gui_copied_url_title": "Адрес OnionShare скопирован", "gui_copied_hidservauth_title": "Строка HidServAuth скопирована", "gui_copied_hidservauth": "Строка HidServAuth скопирована в буфер обмена", - "gui_please_wait": "Начинаем... нажмите, чтобы отменить.", + "gui_please_wait": "Запускается... Нажмите здесь, чтобы отменить.", "gui_download_upload_progress_complete": "%p%, прошло {0:s}.", "gui_download_upload_progress_starting": "{0:s}, %p% (вычисляем)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", @@ -80,33 +80,33 @@ "gui_quit_title": "Не так быстро", "gui_share_quit_warning": "Идёт процесс отправки файлов. Вы уверены, что хотите завершить работу OnionShare?", "gui_receive_quit_warning": "Идёт процесс получения файлов. Вы уверены, что хотите завершить работу OnionShare?", - "error_rate_limit": "Кто-то совершил слишком много неверных попыток подключения к вашему адресу, это может означать, что его пытаются вычислить. OnionShare остановил сервер. Создайте раздачу повторно и перешлите получателю новый адрес.", + "error_rate_limit": "Кто-то совершил слишком много попыток подключения к Вашему серверу отправки файлов. Возможно, его пытаются вычислить. OnionShare остановил сервер. Отправьте Ваши данные повторно и перешлите получателю новый адрес.", "zip_progress_bar_format": "Сжатие: %p%", "error_stealth_not_supported": "Для использования авторизации клиента необходимы как минимум версии Tor 0.2.9.1-alpha (или Tor Browser 6.5) и библиотеки python3-stem 1.5.0.", "error_ephemeral_not_supported": "Для работы OnionShare необходимы как минимум версии Tor 0.2.7.1 и библиотеки python3-stem 1.4.0.", "gui_settings_whats_this": "Что это?", - "gui_settings_stealth_option": "Использовать авторизацию клиента (устарело)", - "gui_settings_stealth_hidservauth_string": "Сохранили ваш приватный ключ для повторного использования,\nзначит теперь вы можете нажать сюда, чтобы скопировать вашу строку HidServAuth.", - "gui_settings_autoupdate_label": "Проверить новую версию", + "gui_settings_stealth_option": "Использовать авторизацию клиента (legacy)", + "gui_settings_stealth_hidservauth_string": "Сохранили Ваш приватный ключ для повторного использования,\nНажмите сюда, чтобы скопировать строку HidServAuth.", + "gui_settings_autoupdate_label": "Проверить наличие новой версии", "gui_settings_autoupdate_option": "Уведомить меня, когда будет доступна новая версия", "gui_settings_autoupdate_timestamp": "Последняя проверка: {}", - "gui_settings_autoupdate_check_button": "Проверить Новую Версию", - "gui_settings_sharing_label": "Настройки раздачи", - "gui_settings_close_after_first_download_option": "Остановить раздачу после первой загрузки", - "gui_settings_connection_type_label": "Как OnionShare следует подключиться к сети Tor?", + "gui_settings_autoupdate_check_button": "Проверить наличие новой версии", + "gui_settings_sharing_label": "Настройки отправки:", + "gui_settings_close_after_first_download_option": "Завершить отправку Ваших файлов\nпосле их первого скачивания", + "gui_settings_connection_type_label": "Как OnionShare следует подключаться к сети Tor?", "gui_settings_connection_type_bundled_option": "Использовать версию Tor, встроенную в OnionShare", - "gui_settings_connection_type_automatic_option": "Попробовать автоматическую настройку с Tor Browser", - "gui_settings_connection_type_control_port_option": "Подключиться используя порт управления", - "gui_settings_connection_type_socket_file_option": "Подключиться используя файл-сокет", + "gui_settings_connection_type_automatic_option": "Автоматическая настройка при помощи Tor Browser", + "gui_settings_connection_type_control_port_option": "Использовать порт управления", + "gui_settings_connection_type_socket_file_option": "Использовать файл сокет", "gui_settings_connection_type_test_button": "Проверить подключение к сети Tor", - "gui_settings_socket_file_label": "Файл-сокет", + "gui_settings_socket_file_label": "Файл сокет", "gui_settings_socks_label": "Порт SOCKS", "gui_settings_authenticate_label": "Настройки аутентификации Tor", "gui_settings_authenticate_no_auth_option": "Без аутентификации или cookie-аутентификации", - "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor", + "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor:", "gui_settings_tor_bridges_no_bridges_radio_option": "Не использовать \"мосты\"", - "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные obfs4 подключаемые транспорты", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные obfs4 подключаемые транспорты (необходим obfs4proxy)", + "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные подключаемые транспорты obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные подключаемые транспорты obfs4 (необходим obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные meek_lite (Azure) встроенные транспорты", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные meek_lite (Azure) встроенные транспорты (необходим obfs4proxy)", "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project

    Используйте их только если Вы не можете поделючться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".", @@ -114,14 +114,14 @@ "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работет.\nПроверьте их снова или добавьте другие.", "gui_settings_shutdown_timeout_checkbox": "Использовать таймер", - "gui_settings_shutdown_timeout": "Остановить раздачу в:", - "settings_error_unknown": "Невозможно подключить к контроллеру Tor, поскольку ваши настройки не корректны.", - "settings_error_automatic": "Невозможно подключться к контроллеру Tor. Уточните, Tor Browser (можно загрузить по ссылке torproject.org) запущен в фоновом режиме?", - "settings_error_socket_port": "Невозможно подключиться к контроллеру Tor в {}:{}.", - "settings_error_socket_file": "Невозможно подключиться к контроллеру Tor используя файл-сокет {}.", + "gui_settings_shutdown_timeout": "Остановить загрузку в:", + "settings_error_unknown": "Невозможно произвести подключение к контроллеру Tor, поскольку Ваши настройки не корректны.", + "settings_error_automatic": "Невозможно произвести подключение к контроллеру Tor. Пожалуйтса, уточните: Tor Browser (можно найти по ссылке: torproject.org) запущен в фоновом режиме?", + "settings_error_socket_port": "Невозможно произвести подключение к контроллеру Tor в {}:{}.", + "settings_error_socket_file": "Невозможно произвести подключение к контроллеру Tor используя файл-сокет {}.", "settings_error_auth": "Произведено подлючение к {}:{}, не получается проверить подлинность. Возможно, это не контроллер сети Tor?", "settings_error_missing_password": "Произведено подключение к контроллеру Tor, но для аутентификации необходим пароль.", - "settings_error_unreadable_cookie_file": "Произведено подключение к контроллеру Tor, но пароль может быть указан неверно или пользователю не разрешено чтение файла-cookie.", + "settings_error_unreadable_cookie_file": "Произведено подключение к контроллеру Tor, но пароль может быть указан неверно или пользователю запрещено чтение файла-cookie.", "settings_error_bundled_tor_not_supported": "Версию Tor, которая поставляется вместе с OnionShare нельзя использовать в режиме разработки на операционных системах Windows или macOS.", "settings_error_bundled_tor_timeout": "Подключение к сети Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет или у вас неточно настроено системное время?", "settings_error_bundled_tor_broken": "OnionShare не смог подулючиться к сети Tor в фоновом режиме:\n{}", @@ -129,57 +129,57 @@ "error_tor_protocol_error": "Произошла ошибка с сетью Tor: {}", "error_tor_protocol_error_unknown": "Произошла неизвестная ошибка с сетю Tor", "error_invalid_private_key": "Данный тип приватного ключа не поддерживается", - "connecting_to_tor": "Подключаемся к сети Tor", + "connecting_to_tor": "Идёт подключение к сети Tor...", "update_available": "Вышла новая версия OnionShare. Нажмите сюда чтобы загрузить.

    Вы используется версию {}, наиболее поздняя версия {}.", "update_error_check_error": "Не удалось проверить новые версии: сайт OnionShare сообщает, что не удалось распознать наиболее позднюю версию '{}'…", - "update_error_invalid_latest_version": "Не удалось проверить наличие новой версии: возможно вы не подключены к сети Tor или сайт OnionShare не работает?", + "update_error_invalid_latest_version": "Не удалось проверить наличие новой версии: возможно, Вы не подключены к сети Tor или сайт OnionShare не работает?", "update_not_available": "Вы используете наиболее позднюю версию OnionShare.", - "gui_tor_connection_ask": "Открыть раздел \"настройки\" для решения проблем с подключением к сети Tor?", + "gui_tor_connection_ask": "Перейти в раздел \"Настройки\" для решения проблем с подключением к сети Tor?", "gui_tor_connection_error_settings": "Попробуйте изменить способ, при помощий которого OnionShare подключается к сети Tor в разделе \"Настройки\".", "gui_tor_connection_canceled": "Не удалось подключиться к сети Tor.\n\nПожалуйста, убедитесь что есть подключение к сети Интернет, затем переоткройте OnionShare и настройте подключение к сети Tor.", "gui_tor_connection_lost": "Произведено отключение от сети Tor.", - "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, создайте новую раздачу.", - "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для того, чтобы начать раздачу.", + "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, отправьте файлы заново.", + "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для того, чтобы начать отправку.", "share_via_onionshare": "OnionShare это", "gui_use_legacy_v2_onions_checkbox": "Используйте устаревшие адреса", - "gui_save_private_key_checkbox": "Используйте постоянный адрес (устарело)", - "gui_share_url_description": "Кто угодно c этим адресом OnionShare может загрузить ваши файлы при помощиTor Browser: ", - "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может выгрузить файлы на ваш комьютер Tor Browser: ", - "gui_url_label_persistent": "Данная раздаче не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", - "gui_url_label_stay_open": "Данная раздача не будет остановлена автоматически.", - "gui_url_label_onetime": "Данная раздача будет завершена автоматически после первой загрузки.", - "gui_url_label_onetime_and_persistent": "Данная раздача не будет завершена автоматически.

    Каждая последующая раздача будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", - "gui_status_indicator_share_stopped": "Можно начинать раздачу", - "gui_status_indicator_share_working": "Начинаем…", - "gui_status_indicator_receive_stopped": "Можно начинать получение", - "gui_status_indicator_receive_working": "Начинаем…", + "gui_save_private_key_checkbox": "Используйте постоянный адрес (legacy)", + "gui_share_url_description": "Кто угодно c этим адресом OnionShare может скачать Ваши файлы при помощи Tor Browser: ", + "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может загрузить файлы на Ваш комьютер Tor Browser: ", + "gui_url_label_persistent": "Данная отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_url_label_stay_open": "Данная отправка не будет остановлена автоматически.", + "gui_url_label_onetime": "Данная отправка будет завершена автоматически после первой загрузки.", + "gui_url_label_onetime_and_persistent": "Данная отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_status_indicator_share_stopped": "Данные готовы к отправке", + "gui_status_indicator_share_working": "Ожидайте...", + "gui_status_indicator_receive_stopped": "Данные готовы к получению", + "gui_status_indicator_receive_working": "Ожидайте...", "gui_file_info": "{} файлы, {}", "gui_file_info_single": "{} файл, {}", "history_in_progress_tooltip": "{} в ходе выполнения", "history_completed_tooltip": "{} завершено", - "info_in_progress_uploads_tooltip": "{} выгрузка(и) в ходе выполнения", - "info_completed_uploads_tooltip": "{} выгрузка(и) завершена(ы)", + "info_in_progress_uploads_tooltip": "{} загрузка(и) в ходе выполнения", + "info_completed_uploads_tooltip": "{} загрузка(и) завершена(ы)", "error_cannot_create_downloads_dir": "Не удалось создать папку в режиме получения: {}", - "receive_mode_downloads_dir": "Файлы, которые были вам отправлены находятся в папке: {}", - "receive_mode_warning": "Внимание: Режим получения позаоляет другия людями загружать файлы на ваш компьютер. Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", - "gui_receive_mode_warning": "Режим получения позаоляет другия людями загружать файлы на ваш компьютер.

    Некоторые файлы могут получить управление вашим компьютером, если вы откроете их. Открывайте только те файлы, которые вы получили от людей, которым вы доверяете, или если вы знаете, что делаете.", - "receive_mode_upload_starting": "Начинается выгрузка общим объёмом {}", + "receive_mode_downloads_dir": "Загруженные Вас файлы находятся в папке: {}", + "receive_mode_warning": "Внимание: Режим получения позволяет другим людями загружать файлы на Ваш компьютер. Некоторые файлы могут представлять угрозу для Вашего компьютера.
    Открывайте файлы полученные только от тех людей, которым Вы доверяете или если Вы знаете, что делаете.", + "gui_receive_mode_warning": "Режим \"Получение Файлов\" позволяет другим людями загружать файлы на Ваш компьютер.

    Некоторые файлы могут представлять угрозу для Вашего компьютера.
    Открывайте файлы полученные только от тех людей, которым Вы доверяете или если Вы знаете, что делаете.
    ", + "receive_mode_upload_starting": "Начинается загрузка общим объёмом {}", "receive_mode_received_file": "Получено: {}", - "gui_mode_share_button": "Раздача файлов", + "gui_mode_share_button": "Отправка файлов", "gui_mode_receive_button": "Получение файлов", - "gui_settings_receiving_label": "Настройки получения", + "gui_settings_receiving_label": "Настройки получения:", "gui_settings_public_mode_checkbox": "Публичный режим", - "systray_close_server_title": "Сервер OnionShare Отключен", + "systray_close_server_title": "Сервер OnionShare отключен", "systray_close_server_message": "Пользователь отключил сервер", - "systray_page_loaded_title": "Страница OnionShare Загружена", - "systray_download_page_loaded_message": "Пользователь находится на странице загрузки", - "systray_upload_page_loaded_message": "Пользователь посетил странцу выгрузки", - "gui_uploads": "История Выгрузок", - "gui_no_uploads": "Пока Нет Выгрузок", - "gui_upload_in_progress": "Выгрузка Началась {}", + "systray_page_loaded_title": "Страница OnionShare загружена", + "systray_download_page_loaded_message": "Пользователь находится на странице скачивания", + "systray_upload_page_loaded_message": "Пользователь посетил странцу загрузки", + "gui_uploads": "История загрузок", + "gui_no_uploads": "Загрузок пока нет", + "gui_upload_in_progress": "Загрузка началась {}", "gui_upload_finished_range": "Загружено {} в {}", - "gui_upload_finished": "Выгружено {}", - "gui_download_in_progress": "Загрузка Началась {}", - "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку nautilus не доступен. Файл находится здесь: {}", - "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки." + "gui_upload_finished": "Загружено {}", + "gui_download_in_progress": "Загрузка началась {}", + "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку файловый менджер Nautilus не доступен. Файл находится здесь: {}", + "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу." } -- cgit v1.2.3-54-g00ecf From a847ab1e3c9f69cc3f7eba347b4708f230633435 Mon Sep 17 00:00:00 2001 From: LocLab fr Date: Mon, 28 Jan 2019 16:47:11 +0000 Subject: Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ --- share/locale/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/locale/fr.json b/share/locale/fr.json index 1661f42c..6405362b 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -210,5 +210,6 @@ "gui_receive_mode_no_files": "Pas encore de fichiers reçus", "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception", "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service Onion", - "systray_share_completed_message": "Terminé l'envoi de fichiers" + "systray_share_completed_message": "Terminé l'envoi de fichiers", + "gui_all_modes_transfer_canceled": "Annulé {}" } -- cgit v1.2.3-54-g00ecf From cdd74632727a76505998ae91e87472f1776ff6d2 Mon Sep 17 00:00:00 2001 From: communia Date: Mon, 28 Jan 2019 14:41:38 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/pt_BR.json | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index fd99384e..0db55231 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Procurar a nova versão", "gui_settings_general_label": "Configurações gerais", "gui_settings_sharing_label": "Compartilhando configurações", - "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", + "gui_settings_close_after_first_download_option": "Parar de compartilhar após o envio dos arquivos", "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", @@ -150,7 +150,7 @@ "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", "gui_file_info_single": "{} arquivo, {}", - "history_in_progress_tooltip": "{} em progresso", + "history_in_progress_tooltip": "{} em curso", "history_completed_tooltip": "{} completado", "info_in_progress_uploads_tooltip": "{} upload(s) em progresso", "info_completed_uploads_tooltip": "{} upload(s) completado(s)", @@ -180,10 +180,33 @@ "gui_upload_finished": "Upload realizado de {}", "gui_download_in_progress": "Download Iniciado {}", "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", - "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicie o OnionShare para que sua alteração de idioma tenha efeito.", + "gui_settings_language_label": "Idioma", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que sua alteração de idioma tenha efeito.", "timeout_upload_still_running": "Esperando o término do upload", "gui_add_files": "Adicionar Documentos", "gui_add_folder": "Adicionar Pasta", - "gui_share_mode_no_files": "Nenhum arquivo ainda enviado" + "gui_share_mode_no_files": "Nenhum arquivo ainda enviado", + "gui_connect_to_tor_for_onion_settings": "Conectar ao Tor para ver as configurações do serviço onion", + "error_cannot_create_data_dir": "Pasta de dados OnionShare não foi criada: {}", + "receive_mode_data_dir": "Os arquivos que lhe foram enviados estão nesta pasta: {}", + "gui_settings_data_dir_label": "Salvar arquivos em", + "gui_settings_data_dir_browse_button": "Navegar", + "systray_share_started_title": "O compartilhamento iniciou", + "systray_share_started_message": "Iniciando o envio de arquivos", + "systray_share_completed_title": "O compartilhamento completou-se", + "systray_share_completed_message": "O envio de arquivos terminou", + "systray_share_canceled_title": "O compartilhamento foi anulado", + "systray_share_canceled_message": "Alguém cancelou o recebimento dos seus arquivos", + "systray_receive_started_title": "O recebimento iniciou", + "systray_receive_started_message": "Alguém está lhe enviando arquivos", + "gui_all_modes_history": "Histórico", + "gui_all_modes_clear_history": "Apagar Tudo", + "gui_all_modes_transfer_started": "Iniciou {}", + "gui_all_modes_transfer_finished_range": "Transferido {} - {}", + "gui_all_modes_transfer_finished": "Transferido {}", + "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", + "gui_all_modes_transfer_canceled": "Cancelado {}", + "gui_share_mode_timeout_waiting": "Esperando para completar o envio", + "gui_receive_mode_no_files": "Nenhum arquivo recebido", + "gui_receive_mode_timeout_waiting": "Esperando para completar o recebimento" } -- cgit v1.2.3-54-g00ecf From 1aaf5702b9eaed76cc45af0b05e2249217cc923f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 28 Jan 2019 14:39:07 -0800 Subject: Weblate (#883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Added translation using Weblate (Hebrew) * Added translation using Weblate (Japanese) * Added translation using Weblate (Shona) * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ * Translated using Weblate (Bulgarian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bg/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Czech) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/cs/ * Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ * Translated using Weblate (Dutch) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nl/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Polish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pl/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ * Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ * Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Norwegian Bokmål) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/nb_NO/ * Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ * Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ * Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ * Translated using Weblate (Swedish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/sv/ * Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ * Translated using Weblate (Turkish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/tr/ * Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ * Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ --- share/locale/da.json | 37 +++++- share/locale/el.json | 154 ++++++++++++++---------- share/locale/es.json | 39 +++++- share/locale/fa.json | 135 ++++++++++++--------- share/locale/fr.json | 113 +++++++++++------- share/locale/it.json | 2 +- share/locale/ja.json | 37 +++++- share/locale/no.json | 39 +++++- share/locale/pt_BR.json | 66 +++++++---- share/locale/ru.json | 5 +- share/locale/sv.json | 61 +++++++--- share/locale/tr.json | 6 +- share/locale/zh_Hans.json | 294 +++++++++++++++++++++++++--------------------- 13 files changed, 633 insertions(+), 355 deletions(-) diff --git a/share/locale/da.json b/share/locale/da.json index fdb456e0..b87f0151 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -9,7 +9,7 @@ "no_available_port": "Kunne ikke finde en tilgængelig port til at starte onion-tjenesten", "other_page_loaded": "Adresse indlæst", "close_on_timeout": "Stoppede fordi timer med autostop løb ud", - "closing_automatically": "Stoppede fordi download er færdig", + "closing_automatically": "Stoppede fordi overførslen er færdig", "timeout_download_still_running": "Venter på at download skal blive færdig", "large_filesize": "Advarsel: Det kan tage timer at sende en stor deling", "systray_menu_exit": "Afslut", @@ -20,7 +20,7 @@ "systray_download_canceled_title": "OnionShare-download annulleret", "systray_download_canceled_message": "Brugeren annullerede downloaden", "help_local_only": "Brug ikke Tor (kun til udvikling)", - "help_stay_open": "Bliv ved med at dele efter første download", + "help_stay_open": "Fortsæt deling efter filerne er blevet sendt", "help_shutdown_timeout": "Stop deling efter et vist antal sekunder", "help_stealth": "Brug klientautentifikation (avanceret)", "help_debug": "Log OnionShare-fejl til stdout, og webfejl til disk", @@ -59,7 +59,7 @@ "gui_settings_autoupdate_timestamp_never": "Aldrig", "gui_settings_autoupdate_check_button": "Søg efter ny version", "gui_settings_sharing_label": "Delingsindstillinger", - "gui_settings_close_after_first_download_option": "Stop deling efter første download", + "gui_settings_close_after_first_download_option": "Stop deling efter filerne er blevet sendt", "gui_settings_connection_type_label": "Hvordan skal OnionShare oprette forbindelse til Tor?", "gui_settings_connection_type_bundled_option": "Brug Tor-versionen som er indbygget i OnionShare", "gui_settings_connection_type_automatic_option": "Prøv autokonfiguration med Tor Browser", @@ -162,7 +162,7 @@ "gui_settings_public_mode_checkbox": "Offentlig tilstand", "systray_close_server_title": "OnionShare-server lukket", "systray_close_server_message": "En bruger lukkede serveren", - "systray_page_loaded_title": "OnionShare-side indlæst", + "systray_page_loaded_title": "Siden er indlæst", "systray_download_page_loaded_message": "En bruger indlæste downloadsiden", "systray_upload_page_loaded_message": "En bruger indlæste uploadsiden", "gui_uploads": "Uploadhistorik", @@ -188,5 +188,32 @@ "timeout_upload_still_running": "Venter på at upload skal blive færdig", "gui_add_files": "Tilføj filer", "gui_add_folder": "Tilføj mappe", - "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste" + "gui_connect_to_tor_for_onion_settings": "Opret forbindelse til Tor for at se indstillinger for onion-tjeneste", + "error_cannot_create_data_dir": "Kunne ikke oprette OnionShare-datamappe: {}", + "receive_mode_data_dir": "Filer som sendes til dig vises i denne mappe: {}", + "gui_settings_data_dir_label": "Gem filer til", + "gui_settings_data_dir_browse_button": "Gennemse", + "systray_page_loaded_message": "OnionShare-adresse indlæst", + "systray_share_started_title": "Deling startet", + "systray_share_started_message": "Start på at sende filer til nogen", + "systray_share_completed_title": "Deling er færdig", + "systray_share_completed_message": "Færdig med at sende filer", + "systray_share_canceled_title": "Deling annulleret", + "systray_share_canceled_message": "Nogen annullerede modtagelsen af dine filer", + "systray_receive_started_title": "Modtagelse startede", + "systray_receive_started_message": "Nogen sender filer til dig", + "gui_all_modes_history": "Historik", + "gui_all_modes_clear_history": "Ryd alle", + "gui_all_modes_transfer_started": "Startede {}", + "gui_all_modes_transfer_finished_range": "Overførte {} - {}", + "gui_all_modes_transfer_finished": "Overførte {}", + "gui_all_modes_progress_complete": "%p%, {0:s} forløbet.", + "gui_all_modes_progress_starting": "{0:s}, %p% (udregner)", + "gui_all_modes_progress_eta": "{0:s}, anslået ankomsttidspunkt: {1:s}, %p%", + "gui_share_mode_no_files": "Der er endnu ikke sendt nogen filer", + "gui_share_mode_timeout_waiting": "Venter på at blive færdig med at sende", + "gui_receive_mode_no_files": "Der er endnu ikke modtaget nogen filer", + "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage", + "gui_all_modes_transfer_canceled_range": "Annullerede {} - {}", + "gui_all_modes_transfer_canceled": "Annullerede {}" } diff --git a/share/locale/el.json b/share/locale/el.json index dcdc97f1..c217716b 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -24,13 +24,13 @@ "systray_upload_started_title": "Η λήψη του OnionShare ξεκίνησε", "systray_upload_started_message": "Ένας/μια χρήστης/τρια ξεκίνησε να ανεβάζει αρχεία στον υπολογιστή σου", "help_local_only": "Να μην χρησιμοποιηθεί το Tor (μόνο για development)", - "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά τη πρώτη λήψη", + "help_stay_open": "Να συνεχίσει ο διαμοιρασμός μετά την αποστολή των αρχείων", "help_shutdown_timeout": "Να τερματιστεί ο διαμοιρασμός μετά από ένα συγκεκριμένο αριθμό δευτερολέπτων", - "help_stealth": "Χρησιμοποιήστε άδεια χρήστη (Για προχωρημένους)", - "help_receive": "", - "help_debug": "", + "help_stealth": "Κάντε χρήση εξουσιοδότησης πελάτη (Για προχωρημένους)", + "help_receive": "Λάβετε διαμοιρασμένα αρχεία αντι να τα στέλνετε", + "help_debug": "Κατέγραψε τα σφάλματα του OnionShare στο stdout (συνήθως οθόνη) και τα σφάλματα web στον δίσκο", "help_filename": "Λίστα αρχείων ή φακέλων για μοίρασμα", - "help_config": "", + "help_config": "Ορίστε σημείο αποθήκευσης αρχείου JSON", "gui_drag_and_drop": "Σύρτε και αφήστε αρχεία και φακέλους\nγια να αρχίσετε να τα μοιράζεστε", "gui_add": "Προσθήκη", "gui_delete": "Διαγραφή", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "Το OnionShare απαιτεί τουλάχιστον το Tor 0.2.7.1 και το python3-stem 1.4.0.", "gui_settings_window_title": "Ρυθμίσεις", "gui_settings_whats_this": " Τί είναι αυτό? ", - "gui_settings_stealth_option": "Χρήση άδειας χρήστη (αδειοδότηση)", + "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη", "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα \nνα επιλέξετε την αντιγραφή του HidServAuth σας.", "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Έλεγχος για νέα έκδοση", "gui_settings_general_label": "Γενικές ρυθμίσεις", "gui_settings_sharing_label": "Ρυθμίσεις κοινοποίησης", - "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης μετά την πρώτη λήψη", + "gui_settings_close_after_first_download_option": "Τερματισμός κοινοποίησης αρχείων μετά την αποστολή τους", "gui_settings_connection_type_label": "Πώς πρέπει να συνδέεται το OnionShare με το Tor?", "gui_settings_connection_type_bundled_option": "Χρησιμοποιήστε την έκδοση του Tor, ενσωματωμένη στο OnionShare", "gui_settings_connection_type_automatic_option": "Προσπάθεια σύνδεσης με τον Tor Browser", @@ -109,67 +109,67 @@ "settings_error_unknown": "Αδύνατη η σύνδεση του ελέγχου Tor, καθώς οι ρυθμίσεις σας δεν έχουν κανένα νόημα.", "settings_error_automatic": "Είναι αδύνατη η σύνδεση στον έλεγχο του Tor. Λειτουργεί ο Tor Browser (διαθέσιμος στο torproject.org) στο παρασκήνιο?", "settings_error_socket_port": "Αδύνατη η σύνδεση στον έλεγχο Tor στις {}:{}.", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "settings_error_socket_file": "Ανέφικτη η σύνδεση με τον ελεγκτή Tor, κάνοντας χρήση αρχείου socket {}.", + "settings_error_auth": "Εγινε σύνδεση με {}:{}, αλλα δεν μπορεί να γίνει πιστοποίηση. Ισως δεν ειναι ενας ελεγκτής Tor?", + "settings_error_missing_password": "Εγινε σύνδεση με ελεγκτή Tor, αλλά απαιτείται κωδικός για πιστοποίηση.", + "settings_error_unreadable_cookie_file": "Εγινε σύνδεση με ελεγκτή Tor, αλλα ο κωδικός πιθανόν να είναι λάθος ή ο χρήστης δεν επιτρέπεται να διαβάζει αρχεία cookie.", + "settings_error_bundled_tor_not_supported": "Η έκδοση Tor που συνοδεύει το OnionShare δεν λειτουργεί σε περιβάλλον προγραμματιστή σε Windows ή macOS.", + "settings_error_bundled_tor_timeout": "Η σύνδεση με Tor αργεί αρκετά. Ισως δεν είστε συνδεδεμένοι στο Διαδίκτυο ή το ρολόι σας δεν ειναι συγχρονισμένο?", + "settings_error_bundled_tor_broken": "Το OnionShare δεν μπορεί να συνδεθεί με το Tor στο παρασκήνιο:\n{}", + "settings_test_success": "Εγινε σύνδεση με τον ελεγκτή Tor.\n\nΕκδοση Tor: {}\nΥποστηρίζει εφήμερες υπηρεσίες onion: {}.\nΥποστηρίζει πιστοποίηση πελάτη: {}.\nΥποστηρίζει νέας γενιάς διευθύνσεις .onion: {}.", + "error_tor_protocol_error": "Υπήρξε σφάλμα με το Tor: {}", + "error_tor_protocol_error_unknown": "Υπήρξε άγνωστο σφάλμα με το Tor", + "error_invalid_private_key": "Αυτο το ιδιωτικό κλειδί δεν υποστηρίζεται", + "connecting_to_tor": "Γίνεται σύνδεση με το δίκτυο Tor", + "update_available": "Βγήκε ενα νέο OnionShare. Κάντε κλικ εδώ για να το λάβετε.

    Χρησιμοποιείτε {} και το πιό πρόσφατο είναι το {}.", + "update_error_check_error": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ο ιστότοπος OnionShare αναφέρει ότι η πιό πρόσφατη έκδοση δεν αναγνωρίζεται '{}'…", + "update_error_invalid_latest_version": "Δεν μπόρεσε να γίνει έλεγχος για νέες εκδόσεις. Ισως δεν είστε συνδεδεμένοι στο Tor ή ο ιστότοπος OnionShare είναι κάτω?", + "update_not_available": "Εχετε την πιό πρόσφατη έκδοση OnionShare.", + "gui_tor_connection_ask": "Να ανοίξετε τις ρυθμίσεις για να επιλύσετε την σύνδεση με το Tor?", + "gui_tor_connection_ask_open_settings": "Ναι", + "gui_tor_connection_ask_quit": "Εξοδος", + "gui_tor_connection_error_settings": "Προσπαθήστε να αλλάξετε τον τρόπο σύνδεσης του OnionShare, με το δίκτυο Tor, από τις ρυθμίσεις.", + "gui_tor_connection_canceled": "Δεν μπόρεσε να γίνει σύνδεση με Tor.\n\nΕλέγξτε ότι είστε συνδεδεμένοι στο Διαδίκτυο, επανεκινήστε το OnionShare και ρυθμίστε την σύνδεση με το Tor.", + "gui_tor_connection_lost": "Εγινε αποσύνδεση απο το Tor.", + "gui_server_started_after_timeout": "Η λειτουργία auto-stop τερματίστηκε πριν την εκκίνηση διακομιστή.\nΠαρακαλώ κάντε εναν νέο διαμοιρασμό.", + "gui_server_timeout_expired": "Η λειτουργία auto-stop ήδη τερματίστηκε.\nΕνημερώστε την για να ξεκινήσετε τον διαμοιρασμό.", + "share_via_onionshare": "Κάντε το OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Χρηση \"παραδοσιακών\" διευθύνσεων", + "gui_save_private_key_checkbox": "Χρήση μόνιμης διεύθυνσης", + "gui_share_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να κατεβάσει τα αρχεία σας με χρήση Φυλλομετρητη Tor: ", + "gui_receive_url_description": "Οποιοσδήποτε με αυτήν την διεύθυνση OnionShare, μπορεί να ανεβάσει αρχεία στον υπολογιστή σας με χρήση του Φυλλομετρητή Tor: ", + "gui_url_label_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

    Οποιοσδήποτε μετέπειτα διαμοιρασμός κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_url_label_stay_open": "Αυτος ο διαμοιρασμός δεν έχει auto-stop.", + "gui_url_label_onetime": "Αυτός ο διαμοιρασμός θα σταματήσει με την πρώτη λήψη.", + "gui_url_label_onetime_and_persistent": "Αυτός ο διαμοιρασμός δεν έχει auto-stop.

    Οποιοσδήποτε μετέπειτα διαμοιρασμός θα κάνει ξανα χρήση αυτής της διεύθυνσης. (Για να κάνετε χρήση διευθύνσεων μιάς φοράς (one-time addresses), απενεργοποιήστε την λειτουργία \"Μόνιμης διεύθυνσης\" στις Ρυθμίσεις.)", + "gui_status_indicator_share_stopped": "Ετοιμο για διαμοιρασμό", + "gui_status_indicator_share_working": "Ξεκινάει…", + "gui_status_indicator_share_started": "Διαμοιράζει", + "gui_status_indicator_receive_stopped": "Ετοιμο για λήψη", + "gui_status_indicator_receive_working": "Ξεκινάει…", + "gui_status_indicator_receive_started": "Γίνεται λήψη", + "gui_file_info": "{} αρχεία, {}", + "gui_file_info_single": "{} αρχείο, {}", + "history_in_progress_tooltip": "{} σε εξέλιξη", + "history_completed_tooltip": "{} ολοκληρώθηκε", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "Προσοχή: η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας. Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", + "gui_receive_mode_warning": "Η λειτουργία λήψης, επιτρέπει άλλους να ανεβάζουν αρχεία στον υπολογιστή σας.

    Μερικά αρχεία πιθανόν να είναι σε θέση να αποκτήσουν τον έλεγχο του υπολογιστή σας εαν τα ανοίξετε. Ανοίξτε μόνο αρχεία που σας εστειλαν άτομα που εμπιστεύεστε ή εαν ξέρετε τι κάνετε.", + "receive_mode_upload_starting": "Αποστολή συνολικού μεγέθους {} ξεκινάει", + "receive_mode_received_file": "Ελήφθη: {}", + "gui_mode_share_button": "Διαμοίρασε αρχεία", + "gui_mode_receive_button": "Λήψη αρχείων", + "gui_settings_receiving_label": "Ρυθμίσεις λήψης", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "Δημόσια λειτουργία", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "Η σελίδα φορτώθηκε", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,8 +179,36 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "", - "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος" + "gui_open_folder_error_nautilus": "Δεν μπορεί να ανοιχτεί ο φάκελος γιατί το nautilus δεν είναι διαθέσιμο. Το αρχείο είναι εδω: {}", + "gui_settings_language_label": "Προτιμώμενη γλώσσα", + "gui_settings_language_changed_notice": "Επανεκινήστε το OnionShare για να ενεργοποιηθεί η αλλαγή γλώσσας.", + "timeout_upload_still_running": "Αναμονή ολοκλήρωσης του ανεβάσματος", + "gui_add_files": "Προσθέστε αρχεία", + "gui_add_folder": "Προσθέστε φάκελο", + "gui_connect_to_tor_for_onion_settings": "Συνδεθείτε με Tor για να δείτε τις ρυθμίσεις της υπηρεσίας onion", + "error_cannot_create_data_dir": "Δεν ήταν δυνατό να δημιουργηθεί φάκελος δεδομένων OnionShare: {}", + "receive_mode_data_dir": "Τα αρχεία που στάλθηκαν σε εσας εμφανίζοντε στον φάκελο: {}", + "gui_settings_data_dir_label": "Αποθήκευσε αρχεία στο", + "gui_settings_data_dir_browse_button": "Περιήγηση", + "systray_page_loaded_message": "Η διεύθυνση OnionShare φορτώθηκε", + "systray_share_started_title": "Ο διαμοιρασμός ξεκίνησε", + "systray_share_started_message": "Ξεκίνησε η αποστολή αρχείων σε κάποιον", + "systray_share_completed_title": "Ο διαμοιρασμός ολοκληρώθηκε", + "systray_share_completed_message": "Ολοκληρώθηκε η αποστολή αρχείων", + "systray_share_canceled_title": "Ο διαμοιρασμός ακυρώθηκε", + "systray_share_canceled_message": "Κάποιος ακύρωσε την λήψη των αρχείων σας", + "systray_receive_started_title": "Η λήψη ξεκίνησε", + "systray_receive_started_message": "Κάποιος σας στέλνει αρχεία", + "gui_all_modes_history": "Ιστορικό", + "gui_all_modes_clear_history": "Καθαρισμός όλων", + "gui_all_modes_transfer_started": "Ξεκινησε {}", + "gui_all_modes_transfer_finished_range": "Μεταφέρθηκαν {} - {}", + "gui_all_modes_transfer_finished": "Μεταφέρθηκαν {} - {}", + "gui_all_modes_progress_complete": "%p%, {0:s} διάρκεια.", + "gui_all_modes_progress_starting": "{0:s}, %p% (γίνεται υπολογισμός)", + "gui_all_modes_progress_eta": "{0:s}, εκτίμηση: {1:s}, %p%", + "gui_share_mode_no_files": "Δεν Στάλθηκαν Αρχεία Ακόμα", + "gui_share_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση αποστολής", + "gui_receive_mode_no_files": "Δεν Εγινε Καμμία Λήψη Αρχείων Ακόμα", + "gui_receive_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση της λήψης" } diff --git a/share/locale/es.json b/share/locale/es.json index ce6c0fd2..3c6452a8 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -4,9 +4,9 @@ "ctrlc_to_stop": "Pulsa Ctrl-C para detener el servidor", "not_a_file": "{0:s} no es un archivo válido.", "other_page_loaded": "La URL está lista", - "closing_automatically": "Apagando automáticamente porque la descarga finalizó", + "closing_automatically": "Detenido porque la transferencia se completó", "help_local_only": "No usar Tor (sólo para desarrollo)", - "help_stay_open": "Mantener el servicio después de que la primera descarga haya finalizado", + "help_stay_open": "Continuar compartiendo luego que los archivos hayan sido enviados", "help_debug": "Enviar los errores de OnionShare a stdout, y los errores web al disco", "help_filename": "Lista de archivos o carpetas para compartir", "gui_drag_and_drop": "Arrastra y suelta archivos y carpetas\npara empezar a compartir", @@ -47,7 +47,7 @@ "gui_settings_tor_bridges": "Soporte de puentes de Tor", "gui_settings_tor_bridges_invalid": "Ninguno de los puentes que has añadido funciona.\nVuelve a verificarlos o añade otros.", "settings_saved": "Ajustes guardados en {}", - "give_this_url_receive": "Dale esta dirección al remitente:", + "give_this_url_receive": "Dele esta dirección al remitente:", "give_this_url_receive_stealth": "Entrega esta dirección y HidServAuth al remitente:", "not_a_readable_file": "{0:s} no es un archivo legible.", "systray_menu_exit": "Salir", @@ -132,7 +132,7 @@ "gui_settings_autoupdate_timestamp_never": "Nunca", "gui_settings_general_label": "Ajustes generales", "gui_settings_sharing_label": "Configuración de compartición", - "gui_settings_close_after_first_download_option": "Dejar de compartir después de la primera descarga", + "gui_settings_close_after_first_download_option": "Dejar de compartir luego que los archivos hayan sido enviados", "gui_settings_connection_type_label": "¿Cómo debería conectarse OnionShare a Tor?", "gui_settings_connection_type_control_port_option": "Conectar usando el puerto de control", "gui_settings_connection_type_socket_file_option": "Conectar usando archivo de socket", @@ -172,7 +172,7 @@ "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare cerrado", "systray_close_server_message": "Un usuario cerró el servidor", - "systray_page_loaded_title": "Página de OnionShare Cargada", + "systray_page_loaded_title": "Página Cargada", "systray_download_page_loaded_message": "Un usuario cargó la página de descarga", "systray_upload_page_loaded_message": "Un usuario cargó la página de carga", "gui_uploads": "Historial de carga", @@ -189,5 +189,32 @@ "timeout_upload_still_running": "Esperando a que se complete la subida", "gui_add_files": "Añadir Archivos", "gui_add_folder": "Añadir Carpeta", - "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla" + "gui_connect_to_tor_for_onion_settings": "Conectarse a Tor para ver configuraciones de servicio cebolla", + "error_cannot_create_data_dir": "No se pudo crear carpeta de datos OnionShare: {}", + "receive_mode_data_dir": "Archivos enviados a usted aparecen en esta carpeta: {}", + "gui_settings_data_dir_label": "Guardar archivos en", + "gui_settings_data_dir_browse_button": "Navegar", + "systray_page_loaded_message": "Dirección OnionShare cargada", + "systray_share_started_title": "Compartir Iniciado", + "systray_share_started_message": "Se empezó a enviar archivos a alguien", + "systray_share_completed_title": "Compartir Completado", + "systray_share_completed_message": "Finalizó envío de archivos", + "systray_share_canceled_title": "Compartir Cancelado", + "systray_share_canceled_message": "Alguien canceló la recepción de sus archivos", + "systray_receive_started_title": "Recepción Iniciada", + "systray_receive_started_message": "Alguien le está enviando archivos", + "gui_all_modes_history": "Historial", + "gui_all_modes_clear_history": "Limpiar Todo", + "gui_all_modes_transfer_started": "Iniciado {}", + "gui_all_modes_transfer_finished_range": "Transferido {} - {}", + "gui_all_modes_transfer_finished": "Transferido {}", + "gui_all_modes_progress_complete": "%p%, {0:s} transcurridos.", + "gui_all_modes_progress_starting": "{0:s}, %p% (calculando)", + "gui_all_modes_progress_eta": "{0:s}, TEA: {1:s}, %p%", + "gui_share_mode_no_files": "No se enviaron archivos todavía", + "gui_share_mode_timeout_waiting": "Esperando a que termine el envío", + "gui_receive_mode_no_files": "No se recibieron archivos todavía", + "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción", + "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", + "gui_all_modes_transfer_canceled": "Cancelado {}" } diff --git a/share/locale/fa.json b/share/locale/fa.json index bca7cc99..eafa64c1 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -8,10 +8,10 @@ "ctrlc_to_stop": "برای توقف سرور Ctrl+C را فشار دهید", "not_a_file": "{0:s} یک فایل معتبر نمی باشد.", "not_a_readable_file": "{0:s} قابل خواندن نمی باشد.", - "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد.", + "no_available_port": "پورت قابل استفاده برای شروع سرویس onion پیدا نشد", "other_page_loaded": "آدرس بارگذاری شد", - "close_on_timeout": "", - "closing_automatically": "متوقف شد چون دانلود به پایان رسید", + "close_on_timeout": "متوقف شد چون تایمر توقف خودکار به پایان رسید", + "closing_automatically": "متوقف شد چون انتقال انجام شد", "timeout_download_still_running": "انتظار برای تکمیل دانلود", "large_filesize": "هشدار: یک اشتراک گذاری بزرگ ممکن است ساعت ها طول بکشد", "systray_menu_exit": "خروج", @@ -23,26 +23,26 @@ "systray_download_canceled_message": "کاربر دانلود را لغو کرد", "systray_upload_started_title": "آپلود OnionShare آغاز شد", "systray_upload_started_message": "یک کاربر شروع به آپلود فایل بر روی کامپیوتر شما کرده است", - "help_local_only": "", - "help_stay_open": "ادامه اشتراک گذاری پس از اولین دانلود", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", + "help_local_only": "عدم استفاده از Tor (فقط برای توسعه)", + "help_stay_open": "ادامه اشتراک گذاری پس از ارسال دانلود ها", + "help_shutdown_timeout": "توقف به اشتراک گذاری پس از میزان ثانیه ای مشخص", + "help_stealth": "استفاده از احراز هویت کلاینت (پیشرفته)", + "help_receive": "دریافت اشتراک به جای ارسال آن", + "help_debug": "لاگ کردن خطاهای OnionShare روی stdout، و خطاهای وب بر روی دیسک", "help_filename": "لیست فایل ها یا فولدر ها برای به اشتراک گذاری", - "help_config": "", - "gui_drag_and_drop": "", + "help_config": "مکان فایل کانفیگ JSON کاستوم (اختیاری)", + "gui_drag_and_drop": "فایل ها و پوشه ها را بکشید و رها کنید\nتا اشتراک گذاری آغاز شود", "gui_add": "افزودن", "gui_delete": "حذف", "gui_choose_items": "انتخاب", "gui_share_start_server": "شروع اشتراک گذاری", "gui_share_stop_server": "توقف اشتراک گذاری", "gui_share_stop_server_shutdown_timeout": "توقف اشتراک گذاری ({} ثانیه باقیمانده)", - "gui_share_stop_server_shutdown_timeout_tooltip": "", + "gui_share_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} متوقف می شود", "gui_receive_start_server": "شروع حالت دریافت", "gui_receive_stop_server": "توقف حالت دریافت", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", + "gui_receive_stop_server_shutdown_timeout": "توقف حالت دریافت ({} ثانیه باقیمانده)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "تایمر توقف خودکار در {} به پایان می رسد", "gui_copy_url": "کپی آدرس", "gui_copy_hidservauth": "کپی HidServAuth", "gui_downloads": "دانلود تاریخچه", @@ -56,31 +56,31 @@ "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", - "version_string": "", + "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "نه به این سرعت", "gui_share_quit_warning": "شما در پروسه ارسال فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_receive_quit_warning": "شما در پروسه دریافت فایل می باشید. مطمئن هستید که میخواهید از OnionShare خارج شوید؟", "gui_quit_warning_quit": "خروج", "gui_quit_warning_dont_quit": "لغو", - "error_rate_limit": "", + "error_rate_limit": "شخصی تعداد زیادی قصد ناصحیح روی آدرس شما داشته است، این می تواند بدین معنا باشد که در حال تلاش برای حدس زدن آن هستند، بنابراین OnionShare سرور را متوقف کرده است. دوباره اشتراک گذاری را آغاز کنید و به گیرنده یک آدرس جدید برای اشتراک ارسال کنید.", "zip_progress_bar_format": "فشرده سازی: %p%", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_stealth_not_supported": "برای استفاده از احراز هویت کلاینت، شما نیاز به داشتن Tor 0.2.9.1-alpha (یا مرورگر Tor 6.5) و python3-stem 1.5.0 دارید.", + "error_ephemeral_not_supported": "OnionShare حداقل به Tor 0.2.7.1 و python3-stem 1.4.0 نیاز دارد.", "gui_settings_window_title": "تنظیمات", "gui_settings_whats_this": "این چیست؟", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "gui_settings_stealth_option": "استفاده از احراز هویت کلاینت", + "gui_settings_stealth_hidservauth_string": "ذخیره کردن کلید خصوصی برای استفاده دوباره، بدین معناست که الان می توانید\nبرای کپی HidServAuth کلیک کنید.", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_timestamp": "آخرین بررسی: {}", "gui_settings_autoupdate_timestamp_never": "هرگز", "gui_settings_autoupdate_check_button": "بررسی برای نسخه جدید", "gui_settings_general_label": "تنظیمات کلی", "gui_settings_sharing_label": "تنظیمات اشتراک گذاری", - "gui_settings_close_after_first_download_option": "توقف اشتراک پس از اولین دانلود", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", + "gui_settings_close_after_first_download_option": "توقف اشتراک گذاری پس از اولین ارسال دانلود", + "gui_settings_connection_type_label": "OnionShare چگونه به Tor باید متصل شود؟", + "gui_settings_connection_type_bundled_option": "استفاده از نسخه Tor قرار گرفته در OnionShare", + "gui_settings_connection_type_automatic_option": "اعمال پیکربندی خودکار با مرورگر Tor", "gui_settings_connection_type_control_port_option": "اتصال از طریق پورت کنترل", "gui_settings_connection_type_socket_file_option": "اتصال از طریق فایل سوکت", "gui_settings_connection_type_test_button": "تست اتصال به Tor", @@ -88,16 +88,16 @@ "gui_settings_socket_file_label": "فایل سوکت‌", "gui_settings_socks_label": "پورت SOCKS", "gui_settings_authenticate_label": "تنظیمات احراز هویت Tor", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_authenticate_no_auth_option": "هیچ احراز هویت، یا احراز هویت کوکی", "gui_settings_authenticate_password_option": "رمز عبور", "gui_settings_password_label": "رمز عبور", - "gui_settings_tor_bridges": "", + "gui_settings_tor_bridges": "پشتیبانی بریج Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "عدم استفاده از بریج", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", + "gui_settings_tor_bridges_obfs4_radio_option": "استفاده از پلاگبل ترنسپورت obfs4", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت obfs4 (نیازمند obfs4proxy)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "استفاده از پلاگبل ترنسپورت meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "استفاده از پلاگبل ترنسپورت meek_lite (Azure) (نیازمند obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "هشدار: بریج های meek_lite برای پروژه Tor بسیار هزینه بر هستند.

    فقط در صورت ناتوانی در اتصال به Tor به صورت مستقیم، از طریق obfs4، یا دیگر بریج ها از آن استفاده کنید.", "gui_settings_tor_bridges_custom_radio_option": "استفاده از بریج های کاستوم", "gui_settings_tor_bridges_custom_label": "میتوانید از https://bridges.torproject.org بریج دریافت کنید", "gui_settings_tor_bridges_invalid": "هیچ کدام از بریج هایی که شما اضافه کردید کار نمی کند.\nآن ها را دوباره چک کنید یا بریج های دیگری اضافه کنید.", @@ -106,24 +106,24 @@ "gui_settings_button_help": "راهنما", "gui_settings_shutdown_timeout_checkbox": "استفاده از تایمر توقف خودکار", "gui_settings_shutdown_timeout": "توقف اشتراک در:", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", + "settings_error_unknown": "ناتوانی در اتصال به کنترل کننده Tor بدلیل نامفهوم بودن تنظیمات.", + "settings_error_automatic": "ناتوانی در اتصال به کنترل کننده Tor. آیا مرورگر Tor (در دسترس از طریق torproject.org) در پس زمینه در حال اجراست؟", + "settings_error_socket_port": "ناتوانی در اتصال به کنترل کننده Tor در {}:{}.", + "settings_error_socket_file": "ناتوانی در اتصال به کنترل کننده Tor از طریق فایل سوکت {}.", + "settings_error_auth": "متصل به {}:{}، اما ناتوانی در احراز هویت. شاید این یک کنترل کننده Tor نمی باشد؟", + "settings_error_missing_password": "متصل به کنترل کننده Tor، اما نیاز به یک رمز عبور برای احراز هویت می باشد.", + "settings_error_unreadable_cookie_file": "اتصال به کنترل کننده Tor برقرار است، اما رمز عبور ممکن است اشتباه باشد، یا کاربری شما اجازه خواندن فایل کوکی را ندارد.", + "settings_error_bundled_tor_not_supported": "استفاده از نسخه Tor که با OnionShare می آید در حالت توسعه روی ویندوز یا مک کار نمی کند.", + "settings_error_bundled_tor_timeout": "اتصال به Tor زمان زیادی می برد. شاید شما به اینترنت متصل نیستید، یا ساعت سیستم شما دقیق نیست؟", + "settings_error_bundled_tor_broken": "OnionShare نمی تواند در پس زمینه به Tor متصل شود:\n{}", + "settings_test_success": "اتصال به کنترل کننده Tor برقرار است.\n\nنسخه Tor: {}\nسرویس های onion ناپایدار پشتیبانی شده: {}.\nاحراز هویت کلاینت پشتیبانی شده: {}.\nپشتیبانی از آدرس های .onion نسل بعدی: {}.", + "error_tor_protocol_error": "خطایی با Tor وجود داشت: {}", + "error_tor_protocol_error_unknown": "خطای ناشناخته ای با Tor وجود داشت", "error_invalid_private_key": "این نوع کلید خصوصی پشتیبانی نمی شود", "connecting_to_tor": "در حال اتصال به شبکه Tor", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", + "update_available": "نسخه جدید OnionShare وجود دارد. اینجا کلیک کنید تا آن را دریافت کنید.

    شما در حال استفاده از {} می باشید و آخرین نسخه {} می باشد.", + "update_error_check_error": "ناتوانی در بررسی برای نسخه های جدید: سایت OnionShare میگوید که آخرین نسخه '{}' ناشناس می باشد…", + "update_error_invalid_latest_version": "ناتوانی در بررسی نسخه جدید: شاید شما به Tor متصل نیستید، یا سایت OnionShare کار نمی کند؟", "update_not_available": "شما از آخرین نسخه OnionShare استفاده می کنید.", "gui_tor_connection_ask": "باز کردن تنظیمات برای ساماندهی اتصال به Tor؟", "gui_tor_connection_ask_open_settings": "بله", @@ -136,12 +136,12 @@ "share_via_onionshare": "OnionShare کنید", "gui_use_legacy_v2_onions_checkbox": "استفاده از آدرس های بازمانده", "gui_save_private_key_checkbox": "استفاده از یک آدرس پایا", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", + "gui_share_url_description": "هرکس با این آدرس OnionShare میتواند روی کامپیوتر شما فایل دانلود کند از طریق مرورگر تور: ", + "gui_receive_url_description": "هرکس با این آدرس OnionShare میتواند روی کامپیوتر شما فایل آپلود کند از طریق مرورگر تور: ", + "gui_url_label_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.

    هر اشتراک بعدی از آدرس دوباره استفاده می کند. ( برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)", "gui_url_label_stay_open": "این اشتراک به صورت خودکار متوقف نمی شود.", "gui_url_label_onetime": "این اشتراک پس از اولین تکمیل متوقف خواهد شد.", - "gui_url_label_onetime_and_persistent": "", + "gui_url_label_onetime_and_persistent": "این اشتراک به صورت خودکار متوقف نمی شود.

    هر اشتراک بعدی از آدرس دوباره استفاده میکند. (برای استفاده از آدرس های یکبار مصرف، از تنظیمات \"استفاده از آدرس پایا\" را غیرفعال کنید.)", "gui_status_indicator_share_stopped": "آماده به اشتراک گذاری", "gui_status_indicator_share_working": "در حال شروع…", "gui_status_indicator_share_started": "در حال اشتراک گذاری", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "حالت عمومی", "systray_close_server_title": "سرور OnionShare بسته شد", "systray_close_server_message": "یک کاربر سرور را بست", - "systray_page_loaded_title": "صفحه OnionShare بارگذاری شد", + "systray_page_loaded_title": "صفحه بارگذاری شد", "systray_download_page_loaded_message": "یک کاربر صفحه دانلود را بارگذاری کرد", "systray_upload_page_loaded_message": "یک کاربر صفحه آپلود را بارگذاری کرد", "gui_uploads": "تاریخچه آپلود", @@ -185,5 +185,32 @@ "timeout_upload_still_running": "انتظار برای تکمیل آپلود", "gui_add_files": "افزودن فایل ها", "gui_add_folder": "افزودن پوشه", - "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion" + "gui_connect_to_tor_for_onion_settings": "اتصال به Tor برای دیدن تنظیمات سرویس onion", + "error_cannot_create_data_dir": "ناتوانی در ایجاد پوشه داده OnionShare: {}", + "receive_mode_data_dir": "فایل های ارسال شده به شما در این پوشه پدیدار خواهند شد: {}", + "gui_settings_data_dir_label": "ذخیره فایل ها در", + "gui_settings_data_dir_browse_button": "مرور", + "systray_page_loaded_message": "آدرس OnionShare بارگذاری شد", + "systray_share_started_title": "اشتراک گذاری آغاز شد", + "systray_share_started_message": "آغاز ارسال فایل به شخصی", + "systray_share_completed_title": "اشتراک گذاری تکمیل شد", + "systray_share_completed_message": "ارسال فایل ها به پایان رسید", + "systray_share_canceled_title": "اشتراک گذاری لغو شد", + "systray_share_canceled_message": "شخصی دریافت فایل های شما را لغو کرد", + "systray_receive_started_title": "دریافت آغاز شد", + "systray_receive_started_message": "شخصی در حال ارسال فایل به شماست", + "gui_all_modes_history": "تاریخچه", + "gui_all_modes_clear_history": "پاکسازی همه", + "gui_all_modes_transfer_started": "{} آغاز شد", + "gui_all_modes_transfer_finished_range": "{} - {} منتقل شد", + "gui_all_modes_transfer_finished": "{} منتقل شد", + "gui_all_modes_progress_complete": "%p%، {0:s} سپری شد.", + "gui_all_modes_progress_starting": "{0:s}, %p% (در حال محاسبه)", + "gui_all_modes_progress_eta": "{0:s}، تخمین: {1:s}, %p%", + "gui_share_mode_no_files": "هیچ فایلی هنوز ارسال نشده است", + "gui_share_mode_timeout_waiting": "انتظار برای به پایان رسیدن ارسال", + "gui_receive_mode_no_files": "هیچ فایلی هنوز دریافت نشده است", + "gui_receive_mode_timeout_waiting": "انتظار برای به پایان رسیدن دریافت", + "gui_all_modes_transfer_canceled_range": "{} - {} لغو شد", + "gui_all_modes_transfer_canceled": "{} لغو شد" } diff --git a/share/locale/fr.json b/share/locale/fr.json index 275fd80a..6405362b 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -1,10 +1,10 @@ { "preparing_files": "Compression des fichiers.", - "give_this_url": "Donnez cette adresse au destinataire :", + "give_this_url": "Donnez cette adresse au destinataire :", "ctrlc_to_stop": "Appuyez sur Ctrl+c pour arrêter le serveur", - "not_a_file": "{0:s} n'est pas un fichier valide.", - "other_page_loaded": "Adresse chargée", - "closing_automatically": "Arrêt automatique car le téléchargement est fini", + "not_a_file": "{0:s} n’est pas un fichier valide.", + "other_page_loaded": "L’adresse a été chargée", + "closing_automatically": "Arrêté, car le transfert est fini", "systray_menu_exit": "Quitter", "systray_download_started_title": "Téléchargement OnionShare démarré", "systray_download_started_message": "Une personne télécharge vos fichiers", @@ -12,14 +12,14 @@ "systray_download_canceled_title": "Téléchargement OnionShare annulé", "systray_download_canceled_message": "La personne a annulé le téléchargement", "help_local_only": "Ne pas utiliser Tor (uniquement pour le développement)", - "help_stay_open": "Continuer le partage après le premier téléchargement", - "help_debug": "Enregistrer les erreurs OnionShare sur la sortie standard, et les erreurs web sur le disque", + "help_stay_open": "Continuer le partage après l’envoi des fichiers", + "help_debug": "Journaliser les erreurs d’OnionShare sur la sortie standard et les erreurs Web sur le disque", "help_filename": "Liste des fichiers ou dossiers à partager", - "gui_drag_and_drop": "Glissez-déposez les fichiers et dossiers\npour commencer à partager", + "gui_drag_and_drop": "Glisser-déposer des fichiers et dossiers\npour commencer le partage", "gui_add": "Ajouter", "gui_delete": "Supprimer", "gui_choose_items": "Sélectionner", - "gui_share_start_server": "Commencer à partager", + "gui_share_start_server": "Commencer le partage", "gui_share_stop_server": "Arrêter le partage", "gui_copy_url": "Copier l'adresse", "gui_copy_hidservauth": "Copier HidServAuth", @@ -32,16 +32,16 @@ "gui_settings_autoupdate_timestamp_never": "Jamais", "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", - "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", - "give_this_url_receive": "Donnez cette adresse à l'expéditeur :", - "give_this_url_receive_stealth": "Donnez cette adresse et HidServAuth à l'expéditeur :", - "not_a_readable_file": "{0:s} n'est pas un fichier lisible.", + "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", + "give_this_url_receive": "Donnez cette adresse à l’expéditeur :", + "give_this_url_receive_stealth": "Donnez cette adresse et cette ligne HidServAuth à l'expéditeur :", + "not_a_readable_file": "{0:s} n’est pas un fichier lisible.", "timeout_download_still_running": "En attente de la fin du téléchargement", "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers", "gui_copied_hidservauth_title": "HidServAuth copié", "gui_settings_window_title": "Paramètres", "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", - "gui_settings_close_after_first_download_option": "Arrêt du partage après le premier téléchargement", + "gui_settings_close_after_first_download_option": "Arrêter le partage après envoi des fichiers", "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", @@ -50,14 +50,14 @@ "gui_settings_authenticate_no_auth_option": "Pas d'authentification ou authentification par cookie", "gui_settings_authenticate_password_option": "Mot de passe", "gui_settings_password_label": "Mot de passe", - "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de bridge", + "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de pont", "gui_settings_button_save": "Sauvegarder", "gui_settings_button_cancel": "Annuler", "gui_settings_button_help": "Aide", - "gui_settings_shutdown_timeout": "Arrêter le partage à :", + "gui_settings_shutdown_timeout": "Arrêter le partage à :", "connecting_to_tor": "Connexion au réseau Tor", - "help_config": "Emplacement du fichier de configuration JSON personnalisé (optionnel)", - "large_filesize": "Avertissement : envoyer un gros fichier peut prendre plusieurs heures", + "help_config": "Emplacement du fichier personnalisé de configuration JSON (facultatif)", + "large_filesize": "Avertissement : envoyer un gros partage peut prendre des heures", "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", "version_string": "OnionShare {0:s} | https://onionshare.org/", "zip_progress_bar_format": "Compression : %p%", @@ -83,10 +83,10 @@ "gui_settings_connection_type_test_button": "Tester la connexion à Tor", "gui_settings_control_port_label": "Port de contrôle", "gui_settings_authenticate_label": "Paramètres d'authentification de Tor", - "gui_settings_tor_bridges": "Support des bridges Tor", - "gui_settings_tor_bridges_custom_radio_option": "Utiliser des bridges personnalisés", - "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des bridges à l'adresse https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Aucun des bridges que vous avez ajouté ne fonctionne.\nVérifiez les à nouveau ou ajoutez-en d'autres.", + "gui_settings_tor_bridges": "Prise en charge des ponts de Tor", + "gui_settings_tor_bridges_custom_radio_option": "Utiliser des ponts personnalisés", + "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des ponts sur https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Aucun des ponts que vous avez ajoutés ne fonctionne.\nVérifiez-les de nouveau ou ajoutez-en d’autres.", "settings_error_unknown": "Impossible de se connecter au contrôleur Tor car les paramètres n'ont pas de sens.", "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Est-ce que le navigateur Tor (disponible sur torproject.org) fonctionne en arrière-plan ?", "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", @@ -108,11 +108,11 @@ "share_via_onionshare": "Partager via OnionShare", "gui_save_private_key_checkbox": "Utiliser une adresse persistante", "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", - "gui_receive_url_description": "Avec cette adresse OnionShare n'importe qui peut envoyer des fichiers vers votre ordinateur en utilisant le navigateur Tor : ", - "gui_url_label_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants réutiliseront l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", - "gui_url_label_stay_open": "Ce partage ne s'arrêtera pas automatiquement.", - "gui_url_label_onetime": "Ce partage s'arrêtera après le premier téléchargement complété.", - "gui_url_label_onetime_and_persistent": "Ce partage ne s'arrêtera pas automatiquement.

    Tous les partages suivants devraient réutiliser l'adresse. (Pour utiliser des adresses uniques, désactivez \"Utiliser une adresse persistante\" dans les paramètres.)", + "gui_receive_url_description": "Quiconque possède cette adresse OnionShare peut téléverser des fichiers vers votre ordinateur en utilisant le Navigateur Tor : ", + "gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", + "gui_url_label_stay_open": "Ce partage ne s’arrêtera pas automatiquement.", + "gui_url_label_onetime": "Ce partage s’arrêtera une fois que le premier téléchargement sera terminé.", + "gui_url_label_onetime_and_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", "gui_status_indicator_share_stopped": "Prêt à partager", "gui_status_indicator_share_working": "Démarrage…", "gui_status_indicator_share_started": "Partage", @@ -124,8 +124,8 @@ "history_in_progress_tooltip": "{} en cours", "history_completed_tooltip": "{} terminé", "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", - "receive_mode_warning": "Avertissement : le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur. Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", - "gui_receive_mode_warning": "Le mode réception permet à d'autres personnes d'envoyer des fichiers vers votre ordinateur.

    Certains fichiers peuvent potentiellement prendre le contrôle de votre ordinateur si vous les ouvrez. Ouvrez uniquement des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_warning": "Avertissement : le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur. Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "gui_receive_mode_warning": "Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur.

    Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", "receive_mode_received_file": "Reçu : {}", "gui_mode_share_button": "Fichiers partagés", "gui_mode_receive_button": "Fichiers reçus", @@ -144,7 +144,7 @@ "gui_download_in_progress": "Téléchargement démarré {}", "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", "gui_settings_language_label": "Langue préférée", - "help_stealth": "Utilisation de l'autorisation client (avancé)", + "help_stealth": "Utilisation de l’autorisation client (avancé)", "help_receive": "Recevoir des partages au lieu de les envoyer", "gui_receive_start_server": "Démarrer le mode réception", "gui_receive_stop_server": "Arrêter le mode réception", @@ -152,7 +152,7 @@ "gui_download_upload_progress_complete": "%p%, {0:s} écoulées.", "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", - "error_rate_limit": "Une personne a effectué sur votre adresse beaucoup de tentatives qui ont échouées, ce qui veut dire qu'elle essayait de la deviner, ainsi OnionShare a arrêté le serveur. Démarrez le partage à nouveau et envoyez une nouvelle adresse au destinataire pour pouvoir partager.", + "error_rate_limit": "Quelqu’un a effectué trop de tentatives échouées sur votre adresse, ce qui signifie que cette personne pourrait essayer de la deviner. C’est pourquoi OnionShare a arrêté le serveur. Redémarrez le partage et envoyez au destinataire une nouvelle adresse pour partager.", "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", "gui_settings_stealth_option": "Utiliser l'autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", @@ -167,22 +167,49 @@ "info_in_progress_uploads_tooltip": "{} envoi(s) en cours", "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)", "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", - "receive_mode_upload_starting": "Un envoi d'une taille totale de {} a commencé", + "receive_mode_upload_starting": "Un téléversement d’une taille totale de {} commence", "systray_close_server_message": "Une personne a arrêté le serveur", - "systray_page_loaded_title": "Page OnionShare chargée", + "systray_page_loaded_title": "Page chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", - "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", - "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d'arrêt automatique se termine après {}", + "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}", + "gui_receive_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}", "gui_settings_tor_bridges_obfs4_radio_option": "Utiliser les transports enfichables obfs4 intégrés", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (nécessitent obfs4proxy)", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (exige obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (nécessitent obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Avertissement : les bridges meek_lite sont très coûteux à faire fonctionner pour le Tor Project.

    Utilisez les seulement si vous ne pouvez pas vous connecter à Tor directement, via les transports obfs4 ou avec d'autres bridges normaux.", - "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d'arrêt automatique", - "gui_server_started_after_timeout": "La minuterie d'arrêt automatique a expiré avant le démarrage du serveur.\nVeuillez faire un nouveau partage.", - "gui_server_timeout_expired": "La minuterie d'arrêt automatique a déjà expiré.\nVeuillez la mettre à jour pour démarrer le partage.", - "close_on_timeout": "Arrêté parce que la minuterie d'arrêt automatique a expiré", - "gui_add_files": "Ajouter fichiers", - "gui_add_folder": "Ajouter dossier" + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (exige obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

    Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor, par les transports obfs4 ou autres ponts normaux.", + "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d’arrêt automatique", + "gui_server_started_after_timeout": "La minuterie d’arrêt automatique est arrivée au bout de son délai avant le démarrage du serveur.\nVeuillez mettre en place un nouveau partage.", + "gui_server_timeout_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai.\nVeuillez la mettre à jour pour commencer le partage.", + "close_on_timeout": "Arrêté, car la minuterie d’arrêt automatique est arrivée au bout de son délai", + "gui_add_files": "Ajouter des fichiers", + "gui_add_folder": "Ajouter un dossier", + "error_cannot_create_data_dir": "Impossible de créer le dossier de données OnionShare : {}", + "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", + "gui_settings_data_dir_label": "Enregistrer les fichiers dans", + "gui_settings_data_dir_browse_button": "Parcourir", + "systray_page_loaded_message": "Adresse OnionShare chargée", + "systray_share_started_title": "Le partage a commencé", + "systray_share_started_message": "Démarrer l'envoi de fichiers à une personne", + "systray_share_completed_title": "Le partage est terminé", + "systray_share_canceled_title": "Le partage a été annulé", + "systray_share_canceled_message": "Une personne a annulé la réception de vos fichiers", + "systray_receive_started_title": "Réception commencée", + "systray_receive_started_message": "Une personne vous envoie des fichiers", + "gui_all_modes_history": "Historique", + "gui_all_modes_clear_history": "Tout effacer", + "gui_all_modes_transfer_started": "{} démarré", + "gui_all_modes_transfer_finished_range": "Transféré {} - {}", + "gui_all_modes_transfer_finished": "Transféré {}", + "gui_all_modes_progress_complete": "%p%, {0:s} écoulé.", + "gui_all_modes_progress_starting": "{0:s}, %p% (estimation)", + "gui_all_modes_progress_eta": "{0:s}, Fin : {1:s}, %p%", + "gui_share_mode_no_files": "Pas encore de fichiers envoyés", + "gui_share_mode_timeout_waiting": "En attente de la fin de l'envoi", + "gui_receive_mode_no_files": "Pas encore de fichiers reçus", + "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception", + "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service Onion", + "systray_share_completed_message": "Terminé l'envoi de fichiers", + "gui_all_modes_transfer_canceled": "Annulé {}" } diff --git a/share/locale/it.json b/share/locale/it.json index 17d4e770..a21d6c5d 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -85,7 +85,7 @@ "gui_settings_language_changed_notice": "Riavvia OnionShare affinché il cambiamento della tua lingua abbia effetto.", "gui_settings_tor_bridges_custom_radio_option": "Utilizzare ponti personalizzati", "timeout_upload_still_running": "In attesa del completamento dell'upload", - "gui_add_files": "Aggiungi file", + "gui_add_files": "Aggiungi archivi", "gui_add_folder": "Aggiungi una cartella", "gui_settings_connection_type_control_port_option": "Connessione usando la porta di controllo", "gui_settings_connection_type_socket_file_option": "Connessione usando il file di socket", diff --git a/share/locale/ja.json b/share/locale/ja.json index 77ae9b58..fae2109e 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -11,7 +11,7 @@ "no_available_port": "onionサービスを実行するための利用可能ポートを見つかりません", "other_page_loaded": "アドレスはロードされています", "close_on_timeout": "自動タイマーがタイムアウトしたため停止されました", - "closing_automatically": "ダウンロードが完了されたため停止されました", + "closing_automatically": "転送が完了されたため停止されました", "timeout_download_still_running": "ダウンロード完了待ち", "timeout_upload_still_running": "アップロード完了待ち", "large_filesize": "注意:大きいなファイルを送信するに数時間かかるかもしれない", @@ -25,7 +25,7 @@ "systray_upload_started_title": "OnionShareアップロードは開始されました", "systray_upload_started_message": "ユーザーがファイルをアップロードし始めました", "help_local_only": "Torを使わない(開発利用のみ)", - "help_stay_open": "最初ダウンロード後に共有し続けます", + "help_stay_open": "ファイルが送信された後に共有し続けます", "help_shutdown_timeout": "数秒後に共有が停止されます", "help_stealth": "クライアント認証を使う(上級者向け)", "help_receive": "送信の代わりに受信を優先する", @@ -80,7 +80,7 @@ "gui_settings_autoupdate_check_button": "更新をチェックする", "gui_settings_general_label": "一般的設定", "gui_settings_sharing_label": "共有設定", - "gui_settings_close_after_first_download_option": "最初のダウンロード後に停止する", + "gui_settings_close_after_first_download_option": "ファイルが送信された後に停止する", "gui_settings_connection_type_label": "OnionShareがどうやってTorと接続して欲しい?", "gui_settings_connection_type_bundled_option": "OnionShareに組み込まれるTorバージョンを使用する", "gui_settings_connection_type_automatic_option": "Torブラウザと自動設定してみる", @@ -172,7 +172,7 @@ "gui_settings_public_mode_checkbox": "公開モード", "systray_close_server_title": "OnionShareサーバーは閉鎖されました", "systray_close_server_message": "ユーザーがサーバーを閉鎖しました", - "systray_page_loaded_title": "OnionShareページはロードされました", + "systray_page_loaded_title": "ページはロードされました", "systray_download_page_loaded_message": "ユーザーがダウンロードページをロードしました", "systray_upload_page_loaded_message": "ユーザーがアップロードページをロードしました", "gui_uploads": "アップロード履歴", @@ -184,5 +184,32 @@ "gui_download_in_progress": "ダウンロード開始しました {}", "gui_open_folder_error_nautilus": "nautilusを利用できないためフォルダーを開けません。ファイルはここに保存されました: {}", "gui_settings_language_label": "優先言語", - "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。" + "gui_settings_language_changed_notice": "言語設定の変更を実行するにはOnionShareを再起動して下さい。", + "error_cannot_create_data_dir": "OnionShareのデータフォルダーを作成できませんでした: {}", + "receive_mode_data_dir": "受信されるファイルをこのフォルダーにあります: {}", + "gui_settings_data_dir_label": "ファイルの保存", + "gui_settings_data_dir_browse_button": "閲覧", + "systray_page_loaded_message": "OnionShareアドレスはロードされました", + "systray_share_started_title": "共有は始めました", + "systray_share_started_message": "誰かにファイルを通信し始めました", + "systray_share_completed_title": "共有完了", + "systray_share_completed_message": "ファイル送信完了", + "systray_share_canceled_title": "共有は停止されました", + "systray_share_canceled_message": "誰かがファイル受信を停止しました", + "systray_receive_started_title": "受信は始めました", + "systray_receive_started_message": "誰かがファイルを送信しています", + "gui_all_modes_history": "歴史", + "gui_all_modes_clear_history": "すべてクリア", + "gui_all_modes_transfer_started": "始めました {}", + "gui_all_modes_transfer_finished_range": "転送された {} - {}", + "gui_all_modes_transfer_finished": "転送された {}", + "gui_all_modes_transfer_canceled_range": "停止された {} - {}", + "gui_all_modes_transfer_canceled": "停止された {}", + "gui_all_modes_progress_complete": "%p%, 経過時間 {0:s} 。", + "gui_all_modes_progress_starting": "{0:s}, %p% (計算中)", + "gui_all_modes_progress_eta": "{0:s}, 完了予定時刻: {1:s}, %p%", + "gui_share_mode_no_files": "送信されたファイルがまだありません", + "gui_share_mode_timeout_waiting": "送信完了を待機しています", + "gui_receive_mode_no_files": "受信されたファイルがまだありません", + "gui_receive_mode_timeout_waiting": "受信完了を待機しています" } diff --git a/share/locale/no.json b/share/locale/no.json index 073461bb..9d67e6fa 100644 --- a/share/locale/no.json +++ b/share/locale/no.json @@ -25,7 +25,7 @@ "systray_upload_started_title": "OnionShare-opplasting startet", "systray_upload_started_message": "En bruker startet opplasting av filer til din datamaskin", "help_local_only": "Ikke bruk Tor (kun i utviklingsøyemed)", - "help_stay_open": "Fortsett å dele etter første nedlasting", + "help_stay_open": "Fortsett å dele etter at filene har blitt sendt", "help_shutdown_timeout": "Stopp deling etter et gitt antall sekunder", "help_stealth": "Bruk klientidentifisering (avansert)", "help_receive": "Motta delinger istedenfor å sende dem", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "OnionShare krever minst både Tor 0.2.7.1 og pything3-stem 1.4.0.", "gui_settings_window_title": "Innstillinger", "gui_settings_whats_this": "Hva er dette?", - "gui_settings_stealth_option": "Bruk klientidentifisering (gammeldags)", + "gui_settings_stealth_option": "Bruk klientidentifisering", "gui_settings_stealth_hidservauth_string": "Siden du har lagret din private nøkkel for gjenbruk, kan du nå\nklikke for å kopiere din HidServAuth-linje.", "gui_settings_autoupdate_label": "Se etter ny versjon", "gui_settings_autoupdate_option": "Gi meg beskjed når en ny versjon er tilgjengelig", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Se etter ny versjon", "gui_settings_general_label": "Hovedinnstillinger", "gui_settings_sharing_label": "Delingsinnstillinger", - "gui_settings_close_after_first_download_option": "Stopp deling etter første nedlasting", + "gui_settings_close_after_first_download_option": "Stopp deling etter at filene har blitt sendt", "gui_settings_connection_type_label": "Hvordan skal OnionShare koble seg til Tor?", "gui_settings_connection_type_bundled_option": "Bruk Tor-versjonen som kommer med OnionShare", "gui_settings_connection_type_automatic_option": "Forsøk automatisk oppsett med Tor-nettleser", @@ -136,7 +136,7 @@ "gui_server_timeout_expired": "Tidsavbruddsuret har gått ut allerede.\nOppdater det for å starte deling.", "share_via_onionshare": "OnionShare det", "gui_use_legacy_v2_onions_checkbox": "Bruk gammeldagse adresser", - "gui_save_private_key_checkbox": "Bruk en vedvarende adresse (gammeldags)", + "gui_save_private_key_checkbox": "Bruk en vedvarende adresse", "gui_share_url_description": "Alle som har denne OnionShare-adressen kan Laste ned filene dine ved bruk av Tor-nettleseren: ", "gui_receive_url_description": "Alle som har denne OnionShare-adressen kan Laste opp filer til din datamaskin ved bruk av Tor-nettleseren: ", "gui_url_label_persistent": "Delingen vil ikke stoppe automatisk.

    Hver påfølgende deling vil gjenbruke adressen. (For engangsadresser, skru av \"Bruk vedvarende adresse\" i innstillingene.)", @@ -171,7 +171,7 @@ "gui_settings_public_mode_checkbox": "Offentlig modus", "systray_close_server_title": "OnionShare-tjener lukket", "systray_close_server_message": "En bruker stengte tjeneren", - "systray_page_loaded_title": "OnionShare-side lastet", + "systray_page_loaded_title": "Side innlastet", "systray_download_page_loaded_message": "En bruker lastet inn nedlastingssiden", "systray_upload_page_loaded_message": "En bruker lastet inn opplastingssiden", "gui_uploads": "Opplastingshistorikk", @@ -189,5 +189,32 @@ "timeout_upload_still_running": "Venter på at opplastingen fullføres", "gui_add_files": "Legg til filer", "gui_add_folder": "Legg til mappe", - "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger" + "gui_connect_to_tor_for_onion_settings": "Koble til Tor for å se løktjeneste-innstillinger", + "error_cannot_create_data_dir": "Kunne ikke opprette OnionShare-datamappe: {}", + "receive_mode_data_dir": "Filers sendt til deg havner i denne mappen: {}", + "gui_settings_data_dir_label": "Lagre filer i", + "gui_settings_data_dir_browse_button": "Utforsk", + "systray_page_loaded_message": "OnionShare-adresse innlastet", + "systray_share_started_title": "Deling startet", + "systray_share_started_message": "Begynner å sende filer til noen", + "systray_share_completed_title": "Deling fullført", + "systray_share_completed_message": "Forsendelse av filer utført", + "systray_share_canceled_title": "Deling avbrutt", + "systray_share_canceled_message": "Noen avbrøt mottak av filene dine", + "systray_receive_started_title": "Mottak startet", + "systray_receive_started_message": "Noen sender filer til deg", + "gui_all_modes_history": "Historikk", + "gui_all_modes_clear_history": "Tøm alt", + "gui_all_modes_transfer_started": "Startet {}", + "gui_all_modes_transfer_finished_range": "Overført {} - {}", + "gui_all_modes_transfer_finished": "Overført {}", + "gui_all_modes_progress_complete": "%p%, {0:s} forløpt.", + "gui_all_modes_progress_starting": "{0:s}, %p% (kalkulerer)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Ingen filer sendt enda", + "gui_share_mode_timeout_waiting": "Venter på fullføring av forsendelse", + "gui_receive_mode_no_files": "Ingen filer mottatt enda", + "gui_receive_mode_timeout_waiting": "Venter på fullføring av mottak", + "gui_all_modes_transfer_canceled_range": "Avbrutt {} - {}", + "gui_all_modes_transfer_canceled": "Avbrutt {}" } diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index f0c839ef..0db55231 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -3,15 +3,15 @@ "preparing_files": "Comprimindo arquivos.", "give_this_url": "Dar este endereço ao destinatário:", "give_this_url_stealth": "Dar este endereço e linha HidServAuth ao destinatário:", - "give_this_url_receive": "Dar este endereço ao remetente:", - "give_this_url_receive_stealth": "Dar este endereço e HidServAuth ao remetente:", + "give_this_url_receive": "Enviar este endereço à pessoa remetente:", + "give_this_url_receive_stealth": "Dar este endereço e HidServAuth à pessoa remetente:", "ctrlc_to_stop": "Pressione Ctrl+C para interromper o servidor", "not_a_file": "{0:s} não é um ficheiro válido.", "not_a_readable_file": "{0:s} não é um ficheiro legível.", "no_available_port": "Não foi possível encontrar um pórtico disponível para iniciar o serviço onion", "other_page_loaded": "Endereço carregado", "close_on_timeout": "Interrompido ao final da contagem do cronômetro automático", - "closing_automatically": "Interrompido porque o download terminou", + "closing_automatically": "Interrompido após o término da transferência", "timeout_download_still_running": "Esperando que o download termine", "large_filesize": "Aviso: O envio de arquivos grandes pode levar várias horas", "systray_menu_exit": "Sair", @@ -24,7 +24,7 @@ "systray_upload_started_title": "OnionShare começou a carregar", "systray_upload_started_message": "Alguém começou a carregar arquivos no seu computador", "help_local_only": "Não use Tor (unicamente para programação)", - "help_stay_open": "Continuar a compartilhar depois do primeiro download", + "help_stay_open": "Continuar a compartilhar após o envio de documentos", "help_shutdown_timeout": "Parar de compartilhar após um número determinado de segundos", "help_stealth": "Usar autorização de cliente (avançado)", "help_receive": "Receber compartilhamentos ao invés de enviá-los", @@ -68,7 +68,7 @@ "error_ephemeral_not_supported": "OnionShare requer ao menos Tor 0.2.7.1 e python3-stem 1.4.0.", "gui_settings_window_title": "Configurações", "gui_settings_whats_this": "O que é isso?", - "gui_settings_stealth_option": "Usar autorização de cliente (legacy)", + "gui_settings_stealth_option": "Usar autorização de cliente", "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", "gui_settings_autoupdate_label": "Procurar a nova versão", "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Procurar a nova versão", "gui_settings_general_label": "Configurações gerais", "gui_settings_sharing_label": "Compartilhando configurações", - "gui_settings_close_after_first_download_option": "Parar de compartilhar depois do primeiro download", + "gui_settings_close_after_first_download_option": "Parar de compartilhar após o envio dos arquivos", "gui_settings_connection_type_label": "Como OnionShare normalmente conecta-se a Tor?", "gui_settings_connection_type_bundled_option": "Use a versão de Tor que vem junto com OnionShare", "gui_settings_connection_type_automatic_option": "Tentar configuração automática com o Navegador Tor", @@ -131,17 +131,17 @@ "gui_tor_connection_error_settings": "Tente mudar nas configurações a forma como OnionShare se conecta à rede Tor.", "gui_tor_connection_canceled": "Não foi possível conectar à rede Tor.\n\nVerifique se você está conectada à Internet, e então abra OnionShare novamente e configure sua conexão à rede Tor.", "gui_tor_connection_lost": "Desconectado do Tor.", - "gui_server_started_after_timeout": "O tempo se esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", - "gui_server_timeout_expired": "O temporizador já se esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", - "share_via_onionshare": "Compartilhar por meio de OnionShare", + "gui_server_started_after_timeout": "O tempo esgotou antes do servidor iniciar.\nPor favor, crie um novo compartilhamento.", + "gui_server_timeout_expired": "O temporizador já esgotou.\nPor favor, atualize-o antes de começar a compartilhar.", + "share_via_onionshare": "Compartilhar usando OnionShare", "gui_use_legacy_v2_onions_checkbox": "Usar endereços do tipo antigo", - "gui_save_private_key_checkbox": "Usar um endereço persistente (modo antigo)", + "gui_save_private_key_checkbox": "Usar o mesmo endereço", "gui_share_url_description": "Qualquer pessoa com este endereço do OnionShare pode baixar seus arquivos usando o Tor Browser: ", - "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode fazer upload de arquivos para o seu computador usando o Tor Browser: ", - "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_receive_url_description": "Qualquer pessoa com este endereço do OnionShare pode carregar arquivos no seu computador usando o Tor Browser: ", + "gui_url_label_persistent": "Este compartilhamento não vai ser encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar um endereço novo a cada vez, desative a opção \"Usar o mesmo endereço\" nas configurações.)", "gui_url_label_stay_open": "Este compartilhamento não sera encerrado automaticamente.", - "gui_url_label_onetime": "Este compartilhamento será encerrado depois da primeira vez que for utilizado.", - "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desligue a opção \"Usar endereço persistente\" nas configurações.)", + "gui_url_label_onetime": "Este compartilhamento será encerrado após completar uma vez.", + "gui_url_label_onetime_and_persistent": "Este compartilhamento não será encerrado automaticamente.

    Todos os compartilhamentos posteriores reutilizarão este endereço. (Para usar endereços únicos a cada compartilhamento, desative a opção \"Usar o mesmo endereço\" nas configurações.)", "gui_status_indicator_share_stopped": "Pronto para compartilhar", "gui_status_indicator_share_working": "Começando…", "gui_status_indicator_share_started": "Compartilhando", @@ -149,8 +149,8 @@ "gui_status_indicator_receive_working": "Começando…", "gui_status_indicator_receive_started": "Recebendo", "gui_file_info": "{} arquivos, {}", - "gui_file_info_single": "{} ficheiro, {}", - "history_in_progress_tooltip": "{} em progresso", + "gui_file_info_single": "{} arquivo, {}", + "history_in_progress_tooltip": "{} em curso", "history_completed_tooltip": "{} completado", "info_in_progress_uploads_tooltip": "{} upload(s) em progresso", "info_completed_uploads_tooltip": "{} upload(s) completado(s)", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "Modo público", "systray_close_server_title": "Servidor OnionShare encerrado", "systray_close_server_message": "Um usuário encerrou o servidor", - "systray_page_loaded_title": "Página OnionShare Carregada", + "systray_page_loaded_title": "Página Carregada", "systray_download_page_loaded_message": "Um usuário carregou a página de download", "systray_upload_page_loaded_message": "Um usuário carregou a página de upload", "gui_uploads": "Histórico de Uploads", @@ -180,7 +180,33 @@ "gui_upload_finished": "Upload realizado de {}", "gui_download_in_progress": "Download Iniciado {}", "gui_open_folder_error_nautilus": "Não foi possível abrir a pasta porque o nautilus não está disponível. O arquivo está aqui: {}", - "gui_settings_language_label": "Idioma preferido", - "gui_settings_language_changed_notice": "Reinicie o OnionShare para que sua alteração de idioma tenha efeito.", - "timeout_upload_still_running": "Esperando o término do upload" + "gui_settings_language_label": "Idioma", + "gui_settings_language_changed_notice": "Reinicie OnionShare para que sua alteração de idioma tenha efeito.", + "timeout_upload_still_running": "Esperando o término do upload", + "gui_add_files": "Adicionar Documentos", + "gui_add_folder": "Adicionar Pasta", + "gui_share_mode_no_files": "Nenhum arquivo ainda enviado", + "gui_connect_to_tor_for_onion_settings": "Conectar ao Tor para ver as configurações do serviço onion", + "error_cannot_create_data_dir": "Pasta de dados OnionShare não foi criada: {}", + "receive_mode_data_dir": "Os arquivos que lhe foram enviados estão nesta pasta: {}", + "gui_settings_data_dir_label": "Salvar arquivos em", + "gui_settings_data_dir_browse_button": "Navegar", + "systray_share_started_title": "O compartilhamento iniciou", + "systray_share_started_message": "Iniciando o envio de arquivos", + "systray_share_completed_title": "O compartilhamento completou-se", + "systray_share_completed_message": "O envio de arquivos terminou", + "systray_share_canceled_title": "O compartilhamento foi anulado", + "systray_share_canceled_message": "Alguém cancelou o recebimento dos seus arquivos", + "systray_receive_started_title": "O recebimento iniciou", + "systray_receive_started_message": "Alguém está lhe enviando arquivos", + "gui_all_modes_history": "Histórico", + "gui_all_modes_clear_history": "Apagar Tudo", + "gui_all_modes_transfer_started": "Iniciou {}", + "gui_all_modes_transfer_finished_range": "Transferido {} - {}", + "gui_all_modes_transfer_finished": "Transferido {}", + "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", + "gui_all_modes_transfer_canceled": "Cancelado {}", + "gui_share_mode_timeout_waiting": "Esperando para completar o envio", + "gui_receive_mode_no_files": "Nenhum arquivo recebido", + "gui_receive_mode_timeout_waiting": "Esperando para completar o recebimento" } diff --git a/share/locale/ru.json b/share/locale/ru.json index 4f1e0ba6..539b488a 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -181,5 +181,8 @@ "gui_upload_finished": "Загружено {}", "gui_download_in_progress": "Загрузка началась {}", "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку файловый менджер Nautilus не доступен. Файл находится здесь: {}", - "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу." + "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу.", + "gui_add_files": "Добавить файлы", + "gui_add_folder": "Добавить папку", + "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}" } diff --git a/share/locale/sv.json b/share/locale/sv.json index c0e28ec7..f1072332 100644 --- a/share/locale/sv.json +++ b/share/locale/sv.json @@ -3,15 +3,15 @@ "preparing_files": "Komprimera filer.", "give_this_url": "Ge den här adressen till mottagaren:", "give_this_url_stealth": "Ge den här adressen och HidServAuth-raden till mottagaren:", - "give_this_url_receive": "Ge den här adressen till avsändaren:", - "give_this_url_receive_stealth": "Ge den här adressen och HidServAuth-raden till avsändaren:", + "give_this_url_receive": "Ge denna adress till avsändaren:", + "give_this_url_receive_stealth": "Ge denna adress och HidServAuth till avsändaren:", "ctrlc_to_stop": "Tryck ned Ctrl+C för att stoppa servern", "not_a_file": "{0:s} är inte en giltig fil.", "not_a_readable_file": "{0:s} är inte en läsbar fil.", - "no_available_port": "Kunde inte hitta en ledig kort för att starta onion-tjänsten", + "no_available_port": "Kunde inte hitta en ledig kort för att börja onion-tjänsten", "other_page_loaded": "Adress laddad", "close_on_timeout": "Stoppad för att automatiska stopp-timern tiden tog slut", - "closing_automatically": "Stannade för att nedladdningen blev klar", + "closing_automatically": "Stoppad för att hämtningen är klar", "timeout_download_still_running": "Väntar på att nedladdningen ska bli klar", "timeout_upload_still_running": "Väntar på att uppladdningen ska bli klar", "large_filesize": "Varning: Att skicka en stor fil kan ta timmar", @@ -25,22 +25,22 @@ "systray_upload_started_title": "OnionShare Uppladdning Påbörjad", "systray_upload_started_message": "En användare började ladda upp filer på din dator", "help_local_only": "Använd inte Tor (endast för utveckling)", - "help_stay_open": "Fortsätt dela efter första nedladdning", + "help_stay_open": "Fortsätt dela efter att filer har skickats", "help_shutdown_timeout": "Avbryt delning efter ett bestämt antal sekunder", "help_stealth": "Använd klient-auktorisering (avancerat)", "help_receive": "Ta emot delningar istället för att skicka dem", "help_debug": "Logga OnionShare fel till stdout och webbfel till hårddisken", "help_filename": "Lista filer och mappar att dela", "help_config": "Egenvald sökväg för JSON konfigurationsfil (valfri)", - "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att påbörja delning", + "gui_drag_and_drop": "Dra och släpp filer och mappar\nför att börja delning", "gui_add": "Lägg till", "gui_delete": "Radera", "gui_choose_items": "Välj", - "gui_share_start_server": "Påbörja delning", + "gui_share_start_server": "Börja dela", "gui_share_stop_server": "Avbryt delning", "gui_share_stop_server_shutdown_timeout": "Avbryt Delning ({}s kvarstår)", - "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern slutar vid {}", - "gui_receive_start_server": "Starta Mottagarläge", + "gui_share_stop_server_shutdown_timeout_tooltip": "Automatiska stopp-timern avslutar vid {}", + "gui_receive_start_server": "Börja mottagarläge", "gui_receive_stop_server": "Avsluta Mottagarläge", "gui_receive_stop_server_shutdown_timeout": "Avsluta Mottagarläge ({}s kvarstår)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Auto-stop timer avslutas kl {}", @@ -49,8 +49,8 @@ "gui_downloads": "Nedladdningshistorik", "gui_no_downloads": "Inga Nedladdningar Än", "gui_canceled": "Avbruten", - "gui_copied_url_title": "OnionShare Adress Kopierad", - "gui_copied_url": "OnionShare adress kopierad till urklipp", + "gui_copied_url_title": "OnionShare-adress kopierad", + "gui_copied_url": "OnionShare-adress kopierad till urklipp", "gui_copied_hidservauth_title": "HidServAuth Kopierad", "gui_copied_hidservauth": "HidServAuth-rad kopierad till urklipp", "gui_please_wait": "Börjar... klicka för att avbryta.", @@ -78,9 +78,9 @@ "gui_settings_autoupdate_check_button": "Sök efter ny version", "gui_settings_general_label": "Allmänna inställningar", "gui_settings_sharing_label": "Delningsinställningar", - "gui_settings_close_after_first_download_option": "Sluta dela efter första hämtningen", + "gui_settings_close_after_first_download_option": "Fortsätt dela efter att filer har skickats", "gui_settings_connection_type_label": "Hur ska OnionShare ansluta till Tor?", - "gui_settings_connection_type_bundled_option": "Använd Tor-versionen inbyggd i OnionShare", + "gui_settings_connection_type_bundled_option": "Använd Tor-versionen som är inbyggd i OnionShare", "gui_settings_connection_type_automatic_option": "Försök automatisk konfiguration med Tor Browser", "gui_settings_connection_type_control_port_option": "Anslut med kontrollport", "gui_settings_connection_type_socket_file_option": "Anslut med socket-filen", @@ -122,9 +122,9 @@ "error_tor_protocol_error_unknown": "Det fanns ett okänt fel med Tor", "error_invalid_private_key": "Denna privata nyckeltyp stöds inte", "connecting_to_tor": "Ansluter till Tor-nätverket", - "update_available": "Ny OnionShare utgiven. Klicka här för att få det.

    Du använder {} och det senaste är {}.", + "update_available": "Ny OnionShare utgiven. Klicka här för att få den.

    Du använder {} och den senaste är {}.", "update_error_check_error": "Det gick inte att söka efter nya versioner: OnionShare-webbplatsen säger att den senaste versionen är den oigenkännliga '{}'…", - "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: kanske är du inte ansluten till Tor, eller OnionShare-webbplatsen är nere?", + "update_error_invalid_latest_version": "Det gick inte att söka efter ny version: Kanske är du inte ansluten till Tor, eller är OnionShare-webbplatsen nere?", "update_not_available": "Du kör den senaste OnionShare.", "gui_tor_connection_ask": "Öppna inställningarna för att sortera ut anslutning till Tor?", "gui_tor_connection_ask_open_settings": "Ja", @@ -169,7 +169,7 @@ "gui_settings_public_mode_checkbox": "Offentligt läge", "systray_close_server_title": "OnionShare-servern stängd", "systray_close_server_message": "En användare stängde servern", - "systray_page_loaded_title": "OnionShare-sidan lästes in", + "systray_page_loaded_title": "Sidan lästes in", "systray_download_page_loaded_message": "En användare läste in hämtningssidan", "systray_upload_page_loaded_message": "En användare läste in sändningssidan", "gui_uploads": "Sändningshistoriken", @@ -184,5 +184,32 @@ "gui_settings_language_changed_notice": "Starta om OnionShare för att din språkändring ska träda i kraft.", "gui_add_files": "Lägg till filer", "gui_add_folder": "Lägg till mapp", - "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar" + "gui_connect_to_tor_for_onion_settings": "Anslut till Tor för att se onion-tjänst-inställningar", + "error_cannot_create_data_dir": "Det gick inte att skapa OnionShare-datamapp: {}", + "receive_mode_data_dir": "Filer som skickas till dig visas i den här mappen: {}", + "gui_settings_data_dir_label": "Spara filer till", + "gui_settings_data_dir_browse_button": "Bläddra", + "systray_page_loaded_message": "OnionShare-adress lästes in", + "systray_share_started_title": "Delning börjades", + "systray_share_started_message": "Börjar skicka filer till någon", + "systray_share_completed_title": "Delning klar", + "systray_share_completed_message": "Filerna skickades", + "systray_share_canceled_title": "Delning avbruten", + "systray_share_canceled_message": "Någon har avbrutit att ta emot dina filer", + "systray_receive_started_title": "Mottagning startad", + "systray_receive_started_message": "Någon skickar filer till dig", + "gui_all_modes_history": "Historik", + "gui_all_modes_clear_history": "Rensa alla", + "gui_all_modes_transfer_started": "Började {}", + "gui_all_modes_transfer_finished_range": "Överförd {} - {}", + "gui_all_modes_transfer_finished": "Överförd {}", + "gui_all_modes_progress_complete": "%p%, {0} förflutit.", + "gui_all_modes_progress_starting": "{0} %s% (beräkning)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Inga filer har skickats än", + "gui_share_mode_timeout_waiting": "Väntar på att avsluta sändningen", + "gui_receive_mode_no_files": "Inga filer har mottagits ännu", + "gui_receive_mode_timeout_waiting": "Väntar på att avsluta mottagande", + "gui_all_modes_transfer_canceled_range": "Avbröt {} - {}", + "gui_all_modes_transfer_canceled": "Avbröt {}" } diff --git a/share/locale/tr.json b/share/locale/tr.json index ae6a7058..a5f5b429 100644 --- a/share/locale/tr.json +++ b/share/locale/tr.json @@ -22,5 +22,9 @@ "gui_copied_url": "Panoya kopyalanan URL", "gui_please_wait": "Lütfen bekleyin...", "zip_progress_bar_format": "Dosyalar hazırlanıyor: %p%", - "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla." + "config_onion_service": "{0:d} bağlantı noktasında onion servisini ayarla.", + "give_this_url_receive": "Bu adresi gönderene ver:", + "not_a_readable_file": "{0:s} okunabilir bir dosya değil.", + "no_available_port": "Onion servisini başlatmak için uygun bir port bulunamadı", + "close_on_timeout": "Otomatik durma zamanlayıcısının bitmesi nedeniyle durdu" } diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index 958bfb3c..2331e762 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -1,19 +1,19 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", + "config_onion_service": "在端口{0:d}上设置洋葱服务。", + "preparing_files": "正在压缩文件.", + "give_this_url": "把这个地址给收件人:", + "give_this_url_stealth": "向收件人提供此地址和HidServAuth行:", + "give_this_url_receive": "把这个地址交给发件人:", + "give_this_url_receive_stealth": "把这个地址和HidServAuth交给发送者:", + "ctrlc_to_stop": "按Ctrl+C停止服务器", + "not_a_file": "{0:s}不是有效文件。", + "not_a_readable_file": "{0:s}不是可读文件.", + "no_available_port": "找不到可用于开启onion服务的端口", + "other_page_loaded": "地址已加载完成", + "close_on_timeout": "终止 原因:自动停止计时器的时间已到", + "closing_automatically": "终止 原因:传输已完成", "timeout_download_still_running": "", - "large_filesize": "", + "large_filesize": "警告:分享大文件可能会用上数小时", "systray_menu_exit": "退出", "systray_download_started_title": "", "systray_download_started_message": "", @@ -23,153 +23,153 @@ "systray_download_canceled_message": "", "systray_upload_started_title": "", "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", + "help_local_only": "不使用Tor(只限开发测试)", + "help_stay_open": "文件传输完成后继续分享", + "help_shutdown_timeout": "超过给定时间(秒)后,终止分享.", + "help_stealth": "使用服务端认证(高级选项)", + "help_receive": "仅接收分享的文件,不发送", + "help_debug": "将OnionShare错误日志记录到stdout,将web错误日志记录到磁盘", + "help_filename": "要分享的文件或文件夹的列表", + "help_config": "自定义JSON配置文件的路径(可选)", + "gui_drag_and_drop": "将文件或文件夹拖动到这里来开始分享", "gui_add": "添加", "gui_delete": "删除", - "gui_choose_items": "选择", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", + "gui_choose_items": "选取", + "gui_share_start_server": "开始分享", + "gui_share_stop_server": "停止分享", + "gui_share_stop_server_shutdown_timeout": "停止分享(还剩{}秒)", + "gui_share_stop_server_shutdown_timeout_tooltip": "在{}自动停止", + "gui_receive_start_server": "开启接受模式", + "gui_receive_stop_server": "停止接受模式", + "gui_receive_stop_server_shutdown_timeout": "停止接受模式(还剩{}秒)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "在{}自动停止", + "gui_copy_url": "复制地址", + "gui_copy_hidservauth": "复制HidServAuth", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "取消", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_canceled": "已取消", + "gui_copied_url_title": "已复制的OnionShare地址", + "gui_copied_url": "OnionShare地址已复制到剪贴板", + "gui_copied_hidservauth_title": "已复制的HidServAuth", + "gui_copied_hidservauth": "HidServAuth行已复制到剪贴板", + "gui_please_wait": "起始中...点击这里可取消.", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "version_string": "版本: OnionShare {0:s} | https://onionshare.org/", + "gui_quit_title": "再等等", + "gui_share_quit_warning": "您有文件正在传输中...您确定要退出OnionShare吗?", + "gui_receive_quit_warning": "您有文件还正在接收中...您确定要退出OnionShare吗?", "gui_quit_warning_quit": "退出", "gui_quit_warning_dont_quit": "取消", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", - "gui_settings_window_title": "设置中", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", + "error_rate_limit": "有人您对地址发出过多错误请求,这很可能说明有人在尝试猜测您的地址.因此为了安全OinionShare已终止服务.请重新开启分享并且向收件人发送新地址.", + "zip_progress_bar_format": "压缩中: %p%", + "error_stealth_not_supported": "要使用服务端认证,您至少需要的最低版本要求是:Tor 0.2.9.1-alpha (or Tor Browser 6.5)和python3-stem 1.5.0.两者缺一不可,同时需要.", + "error_ephemeral_not_supported": "OnionShare至少同时需要Tor 0.2.7.1和python3-stem 1.4.0来运行.", + "gui_settings_window_title": "设置", + "gui_settings_whats_this": "这是什么?", + "gui_settings_stealth_option": "使用客户端认证", + "gui_settings_stealth_hidservauth_string": "已保存了你的私钥用于重复使用,意味着您现在可以\n点击这里来复制您的HidServAuth.", "gui_settings_autoupdate_label": "检查新版本", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", + "gui_settings_autoupdate_option": "有新版本可用时告知我", + "gui_settings_autoupdate_timestamp": "上次检查更新的时间:{}", "gui_settings_autoupdate_timestamp_never": "从不", - "gui_settings_autoupdate_check_button": "", + "gui_settings_autoupdate_check_button": "检查新版本", "gui_settings_general_label": "常规设置", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", + "gui_settings_sharing_label": "分享设置", + "gui_settings_close_after_first_download_option": "文件发送完成后停止分享", + "gui_settings_connection_type_label": "OnionShare应如何连接Tor?", + "gui_settings_connection_type_bundled_option": "使用OnionShare内置的tor", + "gui_settings_connection_type_automatic_option": "尝试使用Tor Browser(Tor浏览器)的设置", + "gui_settings_connection_type_control_port_option": "用特定端口连接", + "gui_settings_connection_type_socket_file_option": "使用socket文档的设置连接", + "gui_settings_connection_type_test_button": "测试tor连接", "gui_settings_control_port_label": "控制端口", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "Socket配置文档", + "gui_settings_socks_label": "SOCKS 端口", + "gui_settings_authenticate_label": "Tor认证设置", + "gui_settings_authenticate_no_auth_option": "无须认证,或者使用的是cookie认证", "gui_settings_authenticate_password_option": "密码", "gui_settings_password_label": "密码", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_tor_bridges": "Tor网桥设置", + "gui_settings_tor_bridges_no_bridges_radio_option": "不使用网桥", + "gui_settings_tor_bridges_obfs4_radio_option": "使用内置的obfs4 pluggable transports", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "使用内置的obfs4 pluggable transports(需要obfs4代理)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "使用内置meek_lite (Azure) pluggable transports", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "使用内置meek_lite (Azure) pluggable transports (需要obfs4代理)", + "gui_settings_meek_lite_expensive_warning": "警告:meek_lite类型的网桥对Tor流量产生的负担很大,
    请只在无法直接使用tor,且obfs4 transport和其他网桥都无法连接时才使用.
    .", + "gui_settings_tor_bridges_custom_radio_option": "使用自定义网桥", + "gui_settings_tor_bridges_custom_label": "您可以从这里得到网桥地址\nhttps://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "您所添加的网桥无法工作.\n请双击它们或者添加其它网桥.", "gui_settings_button_save": "保存", "gui_settings_button_cancel": "取消", "gui_settings_button_help": "帮助", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", - "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "gui_settings_shutdown_timeout_checkbox": "使用自动停止计时器", + "gui_settings_shutdown_timeout": "在(时间)停止分享", + "settings_error_unknown": "无法连接Tor控制件,因为您的设置无法被理解.", + "settings_error_automatic": "无法连接tor控制件.Tor浏览器是否在后台工作?(从torproject.org可以获得Tor Browser)", + "settings_error_socket_port": "在socket端口{}:{}无法连接tor控制件.", + "settings_error_socket_file": "无法使用socket配置文档的设置连接tor控制件", + "settings_error_auth": "已连接到了{}:{},但是无法认证,也许这不是tor控制件?", + "settings_error_missing_password": "已连接到tor控制件,但需要密码来认证.", + "settings_error_unreadable_cookie_file": "已连接到tor控制件,但可能密码错误,或者没有读取cookie文件的权限.", + "settings_error_bundled_tor_not_supported": "OnionShare自带的Tor无法在Windows或macOS下运行开发者模式", + "settings_error_bundled_tor_timeout": "尝试连接tor的用时过长,也许您的网络有问题,或者是系统时间不准确?", + "settings_error_bundled_tor_broken": "OnionShare无法在后台连接Tor\n{}", + "settings_test_success": "已连接到Tor控制件\n\nTor版本: {}\n支持短期onion服务: {}.\n支持客户端认证: {}.\n支持新一代.onion地址: {}.", + "error_tor_protocol_error": "Tor出现错误: {}", + "error_tor_protocol_error_unknown": "Tor出现未知错误", + "error_invalid_private_key": "不支持这种类型的私钥", + "connecting_to_tor": "正在连接Tor网络", + "update_available": "有新版本的OnionShare可用:请点击这里 来获得.

    您在使用的版本为 {} 最新的可用版本为 {}.", + "update_error_check_error": "无法检查更新:OnionShare官网对最新版本无法识别'{}'…", + "update_error_invalid_latest_version": "无法检查更新:也许您没有连接到Tor?或者OnionShare官网不可用?", + "update_not_available": "您现在运行的OnionShare为最新版本.", + "gui_tor_connection_ask": "打开设置来查看Tor连接?", "gui_tor_connection_ask_open_settings": "是的", "gui_tor_connection_ask_quit": "退出", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "分享", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", + "gui_tor_connection_error_settings": "请尝试在设置中设定OnionShare连接Tor的方式.", + "gui_tor_connection_canceled": "无法连接Tor.\n\n请确保您一连接到网络,然后重启OnionShare并设置Tor连接.", + "gui_tor_connection_lost": "已和Tor断开连接.", + "gui_server_started_after_timeout": "在服务开始之前自动停止计时器的时间已到.\n请建立新的分享.", + "gui_server_timeout_expired": "自动停止计时器的时间已到.\n请更新其设置来开始分享.", + "share_via_onionshare": "用OnionShare来分享", + "gui_use_legacy_v2_onions_checkbox": "使用古老的地址", + "gui_save_private_key_checkbox": "使用长期地址", + "gui_share_url_description": "任何人只要拥有这个OnionShare 地址,都可以用Tor浏览器来从您的设备进行文件下载", + "gui_receive_url_description": "任何人只要拥有这个OnionShare 地址,都可以用Tor浏览器来给你的设备进行文件上传", + "gui_url_label_persistent": "这个分享不会自动停止.

    每个子列分享都会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)", + "gui_url_label_stay_open": "这个分享不会自动停止.", + "gui_url_label_onetime": "这个分享将在初次完成后终止.", + "gui_url_label_onetime_and_persistent": "这个分享不会自动停止.

    每个子列分享都将会重复使用这个地址.(要使用一次性地址, 请在设置中关闭\"使用长期地址\"的选项.)", + "gui_status_indicator_share_stopped": "准备分享", + "gui_status_indicator_share_working": "正在初始话…", + "gui_status_indicator_share_started": "分享中", + "gui_status_indicator_receive_stopped": "准备接收", + "gui_status_indicator_receive_working": "正在初始化…", "gui_status_indicator_receive_started": "正在接收", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "gui_file_info": "{} 个文件, {}", + "gui_file_info_single": "{} 个文件, {}", + "history_in_progress_tooltip": "{}在进行中", + "history_completed_tooltip": "{}已完成", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "警告:接收模式下允许他人对您的设备上传文件.有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.", + "gui_receive_mode_warning": "接收模式下允许他人对您的设备上传文件.

    有一些文件可能有恶意代码并控制您的设备或者造成严重伤害,请只谨慎打开来自您信赖的人的文件,或者确保采取必要的安全措施.", + "receive_mode_upload_starting": "上传文件的大小为{}正在开始", + "receive_mode_received_file": "接收到: {}", + "gui_mode_share_button": "分享文件", + "gui_mode_receive_button": "接收文件", + "gui_settings_receiving_label": "接收设置", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "分享轨迹", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "公共模式", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "页面已加载", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,7 +179,35 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", + "gui_open_folder_error_nautilus": "无法打开文件夹,原因:nautilus不可用.文件在这里: {}", "gui_settings_language_label": "首选语言", - "gui_settings_language_changed_notice": "" + "gui_settings_language_changed_notice": "请重启OnionShare以使您的语言改变设定生效.", + "gui_add_files": "添加文件", + "gui_add_folder": "添加文件夹", + "gui_connect_to_tor_for_onion_settings": "连接Tor来查看onion服务的设置", + "error_cannot_create_data_dir": "无法建立OnionShare文件夹: {}", + "receive_mode_data_dir": "您收到的文件会出现在这个文件夹: {}", + "gui_settings_data_dir_label": "将文件保存到", + "gui_settings_data_dir_browse_button": "浏览", + "systray_page_loaded_message": "OnionShare地址已加载", + "systray_share_started_title": "分享开始", + "systray_share_started_message": "开始向某人发送文件", + "systray_share_completed_title": "分享完成", + "systray_share_completed_message": "文件发送完成", + "systray_share_canceled_title": "分享已取消", + "systray_share_canceled_message": "某人取消了接收您的文件", + "systray_receive_started_title": "接收开始", + "systray_receive_started_message": "某人在向您发送文件", + "gui_all_modes_history": "历史", + "gui_all_modes_clear_history": "清除全部", + "gui_all_modes_transfer_started": "已开始{}", + "gui_all_modes_transfer_finished_range": "已传输 {} - {}", + "gui_all_modes_transfer_finished": "已传输完成 {}", + "gui_all_modes_progress_complete": "%p%, {0:s} 已完成.", + "gui_all_modes_progress_starting": "{0:s}, %p% (计算中)", + "gui_all_modes_progress_eta": "{0:s}, 预计完成时间: {1:s}, %p%", + "gui_share_mode_no_files": "还没有文件发出", + "gui_share_mode_timeout_waiting": "等待结束发送", + "gui_receive_mode_no_files": "还没有接收文件", + "gui_receive_mode_timeout_waiting": "等待接收完成" } -- cgit v1.2.3-54-g00ecf -- cgit v1.2.3-54-g00ecf -- cgit v1.2.3-54-g00ecf From 4c11900a8193efb8b463259feef4549c9d6d01a5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 28 Jan 2019 14:59:57 -0800 Subject: Fix .ru translation --- share/locale/ru.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/share/locale/ru.json b/share/locale/ru.json index 156012c0..539b488a 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -178,17 +178,10 @@ "gui_no_uploads": "Загрузок пока нет", "gui_upload_in_progress": "Загрузка началась {}", "gui_upload_finished_range": "Загружено {} в {}", -<<<<<<< HEAD "gui_upload_finished": "Загружено {}", "gui_download_in_progress": "Загрузка началась {}", "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку файловый менджер Nautilus не доступен. Файл находится здесь: {}", "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу.", -======= - "gui_upload_finished": "Выгружено {}", - "gui_download_in_progress": "Загрузка Началась {}", - "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку nautilus не доступен. Файл находится здесь: {}", - "gui_settings_language_changed_notice": "Перезапустите OnionShare чтобы применились языковые настройки.", ->>>>>>> cdd74632727a76505998ae91e87472f1776ff6d2 "gui_add_files": "Добавить файлы", "gui_add_folder": "Добавить папку", "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}" -- cgit v1.2.3-54-g00ecf From e66c52096e9a343fc3377c3199df3476f7b5afc2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 28 Jan 2019 17:28:20 -0800 Subject: Get Tor 0.3.5.7 binary from Tor Browser 8.0.5 for Windows and Mac --- install/get-tor-osx.py | 6 +++--- install/get-tor-windows.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/get-tor-osx.py b/install/get-tor-osx.py index 61c2a4bf..d966173c 100644 --- a/install/get-tor-osx.py +++ b/install/get-tor-osx.py @@ -35,9 +35,9 @@ import subprocess import requests def main(): - dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.4/TorBrowser-8.0.4-osx64_en-US.dmg' - dmg_filename = 'TorBrowser-8.0.4-osx64_en-US.dmg' - expected_dmg_sha256 = '44433ee2052cf3062e0dc29e640a6ae50db2775bc8939253f5f9d81614f2db07' + dmg_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.5/TorBrowser-8.0.5-osx64_en-US.dmg' + dmg_filename = 'TorBrowser-8.0.5-osx64_en-US.dmg' + expected_dmg_sha256 = '08f0f79181319b74f8ad3a3f8c72a46356ec47f1ca3e22eb42d92e51451d9411' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) diff --git a/install/get-tor-windows.py b/install/get-tor-windows.py index e5a24be9..aad45e94 100644 --- a/install/get-tor-windows.py +++ b/install/get-tor-windows.py @@ -33,9 +33,9 @@ import subprocess import requests def main(): - exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.1/torbrowser-install-8.0.1_en-US.exe' - exe_filename = 'torbrowser-install-8.0.1_en-US.exe' - expected_exe_sha256 = 'bdf81d4282b991a6425c213c7b03b3f5c1f17bb02986b7fe9a1891e577e51639' + exe_url = 'https://archive.torproject.org/tor-package-archive/torbrowser/8.0.5/torbrowser-install-8.0.5_en-US.exe' + exe_filename = 'torbrowser-install-8.0.5_en-US.exe' + expected_exe_sha256 = '860fdd06e4ea8dd4c46f221676251f5cc528676d4e256559ee3831a5f97492f1' # Build paths root_path = os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) working_path = os.path.join(os.path.join(root_path, 'build'), 'tor') -- cgit v1.2.3-54-g00ecf From b42f1c47b86f3ea671b3a937441febb82c07551e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 28 Jan 2019 20:01:51 -0800 Subject: Fix crash that occurs when opening settings when both client authentication and persistence are enabled --- onionshare_gui/settings_dialog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 1fe6dc53..7cec8b92 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -144,10 +144,10 @@ class SettingsDialog(QtWidgets.QDialog): self.use_stealth_widget = QtWidgets.QWidget() self.use_stealth_widget.setLayout(use_stealth_layout) - hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) - hidservauth_details.setWordWrap(True) - hidservauth_details.setMinimumSize(hidservauth_details.sizeHint()) - hidservauth_details.hide() + self.hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string')) + self.hidservauth_details.setWordWrap(True) + self.hidservauth_details.setMinimumSize(self.hidservauth_details.sizeHint()) + self.hidservauth_details.hide() self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth')) self.hidservauth_copy_button.clicked.connect(self.hidservauth_copy_button_clicked) @@ -159,7 +159,7 @@ class SettingsDialog(QtWidgets.QDialog): onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget) onion_settings_layout.addWidget(self.save_private_key_widget) onion_settings_layout.addWidget(self.use_stealth_widget) - onion_settings_layout.addWidget(hidservauth_details) + onion_settings_layout.addWidget(self.hidservauth_details) onion_settings_layout.addWidget(self.hidservauth_copy_button) self.onion_settings_widget = QtWidgets.QWidget() self.onion_settings_widget.setStyleSheet(self.common.css['settings_onion_settings']) @@ -523,7 +523,7 @@ class SettingsDialog(QtWidgets.QDialog): # Legacy v2 mode is forced on if Stealth is enabled self.use_legacy_v2_onions_checkbox.setEnabled(False) if save_private_key and self.old_settings.get('hidservauth_string') != "": - hidservauth_details.show() + self.hidservauth_details.show() self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) -- cgit v1.2.3-54-g00ecf From 2a949802d223860b34886fc26b94575378ad8d67 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 28 Jan 2019 20:30:05 -0800 Subject: Separete onion settings into their own group, and remove css that was breaking the look of the "Copy HidServAuth" button --- onionshare/common.py | 7 ------- onionshare_gui/settings_dialog.py | 28 ++++++++++++++++++---------- share/locale/en.json | 3 ++- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/onionshare/common.py b/onionshare/common.py index 49c69ab5..fcb9ca6d 100644 --- a/onionshare/common.py +++ b/onionshare/common.py @@ -398,13 +398,6 @@ class Common(object): 'settings_connect_to_tor': """ QLabel { font-style: italic; - }""", - - # For some reason, this prevents extra padding around the v2 onion - # settings when viewing in macOS - 'settings_onion_settings': """ - QWidget { - border: 0; }""" } diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 7cec8b92..75a2f59b 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -52,7 +52,7 @@ class SettingsDialog(QtWidgets.QDialog): self.system = platform.system() - # General options + # General settings # Use a slug or not ('public mode') self.public_mode_checkbox = QtWidgets.QCheckBox() @@ -88,6 +88,15 @@ class SettingsDialog(QtWidgets.QDialog): self.shutdown_timeout_widget = QtWidgets.QWidget() self.shutdown_timeout_widget.setLayout(shutdown_timeout_layout) + # General settings layout + general_group_layout = QtWidgets.QVBoxLayout() + general_group_layout.addWidget(self.public_mode_widget) + general_group_layout.addWidget(self.shutdown_timeout_widget) + general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) + general_group.setLayout(general_group_layout) + + # Onion settings + # Label telling user to connect to Tor for onion service settings self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) @@ -162,17 +171,15 @@ class SettingsDialog(QtWidgets.QDialog): onion_settings_layout.addWidget(self.hidservauth_details) onion_settings_layout.addWidget(self.hidservauth_copy_button) self.onion_settings_widget = QtWidgets.QWidget() - self.onion_settings_widget.setStyleSheet(self.common.css['settings_onion_settings']) self.onion_settings_widget.setLayout(onion_settings_layout) - # General options layout - general_group_layout = QtWidgets.QVBoxLayout() - general_group_layout.addWidget(self.public_mode_widget) - general_group_layout.addWidget(self.shutdown_timeout_widget) - general_group_layout.addWidget(self.connect_to_tor_label) - general_group_layout.addWidget(self.onion_settings_widget) - general_group = QtWidgets.QGroupBox(strings._("gui_settings_general_label")) - general_group.setLayout(general_group_layout) + # Onion settings layout + onion_group_layout = QtWidgets.QVBoxLayout() + onion_group_layout.addWidget(self.connect_to_tor_label) + onion_group_layout.addWidget(self.onion_settings_widget) + onion_group = QtWidgets.QGroupBox(strings._("gui_settings_onion_label")) + onion_group.setLayout(onion_group_layout) + # Sharing options @@ -445,6 +452,7 @@ class SettingsDialog(QtWidgets.QDialog): # Layout left_col_layout = QtWidgets.QVBoxLayout() left_col_layout.addWidget(general_group) + left_col_layout.addWidget(onion_group) left_col_layout.addWidget(sharing_group) left_col_layout.addWidget(receiving_group) left_col_layout.addWidget(autoupdate_group) diff --git a/share/locale/en.json b/share/locale/en.json index 3ad2efda..f7af9a80 100644 --- a/share/locale/en.json +++ b/share/locale/en.json @@ -56,13 +56,14 @@ "gui_settings_window_title": "Settings", "gui_settings_whats_this": "What's this?", "gui_settings_stealth_option": "Use client authorization", - "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now\nclick to copy your HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Having saved your private key for reuse, means you can now click to copy your HidServAuth.", "gui_settings_autoupdate_label": "Check for new version", "gui_settings_autoupdate_option": "Notify me when a new version is available", "gui_settings_autoupdate_timestamp": "Last checked: {}", "gui_settings_autoupdate_timestamp_never": "Never", "gui_settings_autoupdate_check_button": "Check for New Version", "gui_settings_general_label": "General settings", + "gui_settings_onion_label": "Onion settings", "gui_settings_sharing_label": "Sharing settings", "gui_settings_close_after_first_download_option": "Stop sharing after files have been sent", "gui_settings_connection_type_label": "How should OnionShare connect to Tor?", -- cgit v1.2.3-54-g00ecf From 9215471ddd9260850b2eb387566f60bf7d1b4f33 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 9 Feb 2019 19:58:04 +0100 Subject: Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ Translated using Weblate (Portuguese (Brazil)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/pt_BR/ Translated using Weblate (Persian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fa/ Translated using Weblate (Korean) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ko/ Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ Translated using Weblate (Greek) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/el/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Translated using Weblate (Finnish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fi/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ Translated using Weblate (Arabic) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ar/ Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ Translated using Weblate (Bengali) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/bn/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (German) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/de/ Translated using Weblate (Italian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/it/ Translated using Weblate (Spanish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/es/ Translated using Weblate (Russian) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ru/ Translated using Weblate (French) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/fr/ Translated using Weblate (Danish) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/da/ Translated using Weblate (Chinese (Simplified)) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/zh_Hans/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Catalan) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ca/ Translated using Weblate (Japanese) Translate-URL: https://hosted.weblate.org/projects/onionshare/translations/ja/ --- share/locale/ar.json | 4 +- share/locale/bn.json | 281 +++++++++++++++++++++++++--------------------- share/locale/ca.json | 129 +++++++++++++-------- share/locale/da.json | 5 +- share/locale/de.json | 42 ++++--- share/locale/el.json | 7 +- share/locale/es.json | 5 +- share/locale/fa.json | 2 +- share/locale/fi.json | 4 +- share/locale/fr.json | 160 +++++++++++++------------- share/locale/it.json | 40 ++++++- share/locale/ja.json | 9 +- share/locale/ko.json | 2 +- share/locale/pt_BR.json | 2 +- share/locale/ru.json | 146 ++++++++++++++---------- share/locale/zh_Hans.json | 5 +- 16 files changed, 486 insertions(+), 357 deletions(-) diff --git a/share/locale/ar.json b/share/locale/ar.json index 5924eb5d..b70127fa 100644 --- a/share/locale/ar.json +++ b/share/locale/ar.json @@ -6,8 +6,8 @@ "give_this_url_receive": "اعط هذا العنوان للمرسل:", "give_this_url_receive_stealth": "أعط هذا العنوان و الخط المحتوى على (HidServAuth) للراسل:", "ctrlc_to_stop": "اضغط (Ctrl+C) لايقاف الخادم", - "not_a_file": "{0:s} ليس ملفا يستطيع البرنامج التعامل معه.", - "not_a_readable_file": "{0:s} ملف لا يمكن قراءته.", + "not_a_file": "{0:s} ليس ملفا صالحا.", + "not_a_readable_file": "{0:s} ملف غير قابل للقراءة.", "no_available_port": "لا يوجد منفذ متاح لتشغيل (onion service)", "other_page_loaded": "تم تحميل العنوان", "close_on_timeout": "", diff --git a/share/locale/bn.json b/share/locale/bn.json index e62db7da..691c5047 100644 --- a/share/locale/bn.json +++ b/share/locale/bn.json @@ -1,20 +1,20 @@ { - "config_onion_service": "", - "preparing_files": "", - "give_this_url": "", - "give_this_url_stealth": "", - "give_this_url_receive": "", - "give_this_url_receive_stealth": "", - "ctrlc_to_stop": "", - "not_a_file": "", - "not_a_readable_file": "", - "no_available_port": "", - "other_page_loaded": "", - "close_on_timeout": "", - "closing_automatically": "", + "config_onion_service": "{0:d} পোর্টে onion সার্ভিস সেটাপ করা হচ্ছে।", + "preparing_files": "ফাইলগুলোকে কমপ্রেস করা হচ্ছে।", + "give_this_url": "প্রাপককে এই এড্রেসটি দিন:", + "give_this_url_stealth": "প্রাপককে এই এড্রেস এবং HidServAuth লাইনটি দিন:", + "give_this_url_receive": "প্রেরককে এই ঠিকানাটি দিন:", + "give_this_url_receive_stealth": "প্রেরককে এই এড্রেস এবং HidServAuthটি দিন:", + "ctrlc_to_stop": "সার্ভারটি বন্ধ করার জন্য Ctrl+C চাপুন", + "not_a_file": "{0:s} ফাইলটি বৈধ নয়।", + "not_a_readable_file": "{0:s} ফাইলটি পড়া যাচ্ছে না।", + "no_available_port": "Onion সার্ভিস চালু করার জন্য কোন পোর্ট পাওয়া যাচ্ছে না", + "other_page_loaded": "এড্রেস লোড হয়েছে", + "close_on_timeout": "বন্ধ করা হয়েছে কারণ অটো-স্টপ টাইমার এর সময় শেষ", + "closing_automatically": "ট্রান্সফার শেষ তাই থেমে যাওয়া হলো", "timeout_download_still_running": "", "timeout_upload_still_running": "", - "large_filesize": "", + "large_filesize": "সতর্কতা: বড় ফাইল পাঠাতে গেলে কয়েক ঘণ্টা লাগতে পারে", "systray_menu_exit": "প্রস্থান করুন", "systray_download_started_title": "", "systray_download_started_message": "", @@ -24,152 +24,152 @@ "systray_download_canceled_message": "", "systray_upload_started_title": "", "systray_upload_started_message": "", - "help_local_only": "", - "help_stay_open": "", - "help_shutdown_timeout": "", - "help_stealth": "", - "help_receive": "", - "help_debug": "", - "help_filename": "", - "help_config": "", - "gui_drag_and_drop": "", + "help_local_only": "Tor ব্যবহার করবে না (শুধুমাত্র ডেভেলপারদের জন্য)", + "help_stay_open": "ফাইলগুলো পাঠানো হয়ে গেলেও শেয়ার করা থামিও না", + "help_shutdown_timeout": "নির্দিষ্ট সেকেন্ডের পর শেয়ার করা বন্ধ করে দিও", + "help_stealth": "ক্লায়েন্ট অনুমোদন ব্যবহার করুন (উন্নততর)", + "help_receive": "কোনকিছু শেয়ার না করে শুধু গ্রহণ করবে", + "help_debug": "OnionShare-এর এররগুলো stdout-এ দেখাও, আর ওয়েব এররগুলো ডিস্কে লগ করো", + "help_filename": "শেয়ার করার জন্য ফাইল বা ফোল্ডারের লিস্ট", + "help_config": "কাস্টম JSON কনফিগারেশন ফাইলের লোকেশন (যদি থাকে)", + "gui_drag_and_drop": "শেয়ার করা শুরু করতে\nফাইল এবং ফোল্ডারগুলো ড্র্যাগ করে ড্রপ করুন", "gui_add": "সংযোজন করুন", "gui_delete": "মুছ", "gui_choose_items": "পছন্দ করুন", - "gui_share_start_server": "", - "gui_share_stop_server": "", - "gui_share_stop_server_shutdown_timeout": "", - "gui_share_stop_server_shutdown_timeout_tooltip": "", - "gui_receive_start_server": "", - "gui_receive_stop_server": "", - "gui_receive_stop_server_shutdown_timeout": "", - "gui_receive_stop_server_shutdown_timeout_tooltip": "", - "gui_copy_url": "", - "gui_copy_hidservauth": "", + "gui_share_start_server": "শেয়ার করা শুরু করো", + "gui_share_stop_server": "শেয়ার করা বন্ধ করো", + "gui_share_stop_server_shutdown_timeout": "শেয়ার করা বন্ধ করো ({} সেকেন্ড বাকি)", + "gui_share_stop_server_shutdown_timeout_tooltip": "টাইমার অনুযায়ী অটোমেটিক বন্ধ হবে {}-তে", + "gui_receive_start_server": "প্রাপ্ত মোড আরম্ভ করুন ", + "gui_receive_stop_server": "প্রাপ্ত মোড বন্ধ করুন ", + "gui_receive_stop_server_shutdown_timeout": "প্রাপ্ত মোড বন্ধ করুন ({}সে বাকি) ", + "gui_receive_stop_server_shutdown_timeout_tooltip": "টাইমার অনুযায়ী অটোমেটিক বন্ধ হবে {}-তে", + "gui_copy_url": "এড্রেস কপি করো", + "gui_copy_hidservauth": "HidServAuth কপি করো", "gui_downloads": "", "gui_no_downloads": "", - "gui_canceled": "", - "gui_copied_url_title": "", - "gui_copied_url": "", - "gui_copied_hidservauth_title": "", - "gui_copied_hidservauth": "", - "gui_please_wait": "", + "gui_canceled": "বাতিল করা হয়েছে", + "gui_copied_url_title": "OnionShare এড্রেস কপি করা হয়েছে", + "gui_copied_url": "OnionShare এড্রেসটি ক্লিপবোর্ডে কপি করা হয়েছে", + "gui_copied_hidservauth_title": "HidServAuth কপি করা হয়েছে", + "gui_copied_hidservauth": "HidServAuth লাইনটি ক্লিপবোর্ডে কপি করা হয়েছে", + "gui_please_wait": "চালু করা হচ্ছে… বন্ধ করার জন্য এখানে ক্লিক করুন।", "gui_download_upload_progress_complete": "", "gui_download_upload_progress_starting": "", "gui_download_upload_progress_eta": "", - "version_string": "", - "gui_quit_title": "", - "gui_share_quit_warning": "", - "gui_receive_quit_warning": "", + "version_string": "OnionShare (অনিয়নশেয়ার) {0:s} | https://onionshare.org/", + "gui_quit_title": "একটু দাড়ান", + "gui_share_quit_warning": "আপনি ফাইল পাঠানোর মধ্যে আছেন। আপনি কি আসলেই OnionShare বন্ধ করতে চান?", + "gui_receive_quit_warning": "আপনি ফাইল গ্রহণের মধ্যে আছেন। আপনি কি আসলেই OnionShare বন্ধ করতে চান?", "gui_quit_warning_quit": "প্রস্থান করুন", "gui_quit_warning_dont_quit": "বাতিল", - "error_rate_limit": "", - "zip_progress_bar_format": "", - "error_stealth_not_supported": "", - "error_ephemeral_not_supported": "", + "error_rate_limit": "কেউ একজন আপনার এড্রেসটিতে অসফলভাবে এক্সেস করার চেষ্টা করেছে, এর মানে তারা আপনার এড্রেসটি আন্দাজ করার চেষ্টা করছে, তাই OnionShare নিরাপত্তার জন্য সার্ভার বন্ধ করে দিয়েছে। নতুন করে শেয়ার করা শুরু করুন এবং প্রাপককে নতুন এড্রেসটি দিন।", + "zip_progress_bar_format": "কমপ্রেস করা হচ্ছে: %p%", + "error_stealth_not_supported": "ক্লায়েন্ট অথোরাইজেশন ব্যবহার করার জন্য, আপনার অন্তত Tor 0.2.9.1-alpha (or Tor Browser 6.5) এবং python3-stem 1.5.0 দুটোই থাকতে হবে।", + "error_ephemeral_not_supported": "OnionShare ব্যবহার করার জন্য Tor 0.2.9.1-alpha (or Tor Browser 6.5) এবং python3-stem 1.5.0 দুটোই থাকতে হবে।", "gui_settings_window_title": "সেটিং", - "gui_settings_whats_this": "", - "gui_settings_stealth_option": "", - "gui_settings_stealth_hidservauth_string": "", - "gui_settings_autoupdate_label": "", - "gui_settings_autoupdate_option": "", - "gui_settings_autoupdate_timestamp": "", - "gui_settings_autoupdate_timestamp_never": "", - "gui_settings_autoupdate_check_button": "", - "gui_settings_general_label": "", - "gui_settings_sharing_label": "", - "gui_settings_close_after_first_download_option": "", - "gui_settings_connection_type_label": "", - "gui_settings_connection_type_bundled_option": "", - "gui_settings_connection_type_automatic_option": "", - "gui_settings_connection_type_control_port_option": "", - "gui_settings_connection_type_socket_file_option": "", - "gui_settings_connection_type_test_button": "", + "gui_settings_whats_this": "এটা কি?", + "gui_settings_stealth_option": "ক্লায়েন্ট অথোরাইজেশন ব্যবহার করো", + "gui_settings_stealth_hidservauth_string": "আপনার প্রাইভেট কি সেভ করে থাকলে, এর মানে হলো এখন আপনি আপনার HidServAuth কপি করার জন্য ক্লিক করতে পারেন।", + "gui_settings_autoupdate_label": "নতুন ভার্সন এসেছে কিনা দেখো", + "gui_settings_autoupdate_option": "নতুন ভার্সন আসলে আমাকে জানাবে", + "gui_settings_autoupdate_timestamp": "সবশেষ চেক করা হয়েছে: {}", + "gui_settings_autoupdate_timestamp_never": "কখনো নয়", + "gui_settings_autoupdate_check_button": "নতুন ভার্সন এসেছে কিনা দেখো", + "gui_settings_general_label": "সাধারণ সেটিংস", + "gui_settings_sharing_label": "শেয়ারের জন্য সেটিংস", + "gui_settings_close_after_first_download_option": "ফাইল পাঠানো হলে শেয়ার করা বন্ধ করে দিও", + "gui_settings_connection_type_label": "OnionShare কিভাবে Tor-এ কানেক্ট করবে?", + "gui_settings_connection_type_bundled_option": "OnionShare-এর নিজস্ব Tor ভার্সনটি ব্যবহার করো", + "gui_settings_connection_type_automatic_option": "Tor Browser থেকে অটোমেটিক কনফিগার করার চেষ্টা করো", + "gui_settings_connection_type_control_port_option": "কন্ট্রোল পোর্ট ব্যবহার করে কানেক্ট করো", + "gui_settings_connection_type_socket_file_option": "সকেট ফাইল দিয়ে কানেক্ট করো", + "gui_settings_connection_type_test_button": "Tor-এর সাথে কানেকশন পরীক্ষা করো", "gui_settings_control_port_label": "নিয়ন্ত্রন পোর্ট", - "gui_settings_socket_file_label": "", - "gui_settings_socks_label": "", - "gui_settings_authenticate_label": "", - "gui_settings_authenticate_no_auth_option": "", + "gui_settings_socket_file_label": "সকেট ফাইল", + "gui_settings_socks_label": "SOCKS পোর্ট", + "gui_settings_authenticate_label": "Tor অথেনটিকেশন সেটিংস", + "gui_settings_authenticate_no_auth_option": "অথেনটিকেশন ছাড়া, বা কুকি অথেনটিকেশন", "gui_settings_authenticate_password_option": "পাসওয়ার্ড", "gui_settings_password_label": "পাসওয়ার্ড", - "gui_settings_tor_bridges": "", - "gui_settings_tor_bridges_no_bridges_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option": "", - "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "", - "gui_settings_meek_lite_expensive_warning": "", - "gui_settings_tor_bridges_custom_radio_option": "", - "gui_settings_tor_bridges_custom_label": "", - "gui_settings_tor_bridges_invalid": "", + "gui_settings_tor_bridges": "Tor ব্রিজ সাপোর্ট", + "gui_settings_tor_bridges_no_bridges_radio_option": "ব্রিজ ব্যবহার করো না", + "gui_settings_tor_bridges_obfs4_radio_option": "নিজস্ব obfs4 প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো", + "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "নিজস্ব obfs4 প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো (obfs4proxy লাগবে)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "নিজস্ব meek_lite (Azure) প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "নিজস্ব meek_lite (Azure) প্লাগেবল ট্রান্সপোর্ট ব্যবহার করো (obfs4proxy লাগবে)", + "gui_settings_meek_lite_expensive_warning": "সতর্কতা: meek_lite ব্রিজ পরিচালনা করা Tor Project-এর জন্য অনেক ব্যয়বহুল।

    এগুলো তখনই ব্যবহার করুন যখন Tor-এ সরাসরি কানেক্ট করতে পারছেন না, obfs4 ট্রান্সপোর্ট দিয়ে, অথবা অন্যান্য সাধারণ ব্রিজ দিয়ে।", + "gui_settings_tor_bridges_custom_radio_option": "কাস্টম ব্রিজ ব্যবহার করো", + "gui_settings_tor_bridges_custom_label": "ব্রিজ পেতে চাইলে https://bridges.torproject.org দেখুন", + "gui_settings_tor_bridges_invalid": "আপনার দেয়া কোন ব্রিজই কাজ করছে না।\nআরেকবার চেক করে দেখুন বা নতুন ব্রিজ দিয়ে চেষ্টা করুন।", "gui_settings_button_save": "সেভ", "gui_settings_button_cancel": "বাতিল", "gui_settings_button_help": "সাহায্য", - "gui_settings_shutdown_timeout_checkbox": "", - "gui_settings_shutdown_timeout": "", - "settings_error_unknown": "", - "settings_error_automatic": "", - "settings_error_socket_port": "", - "settings_error_socket_file": "", - "settings_error_auth": "", - "settings_error_missing_password": "", - "settings_error_unreadable_cookie_file": "", - "settings_error_bundled_tor_not_supported": "", - "settings_error_bundled_tor_timeout": "", - "settings_error_bundled_tor_broken": "", - "settings_test_success": "", - "error_tor_protocol_error": "", - "error_tor_protocol_error_unknown": "", - "error_invalid_private_key": "", - "connecting_to_tor": "", + "gui_settings_shutdown_timeout_checkbox": "কানেকশন বন্ধ করার জন্য অটোমেটিক টাইমার ব্যবহার করো", + "gui_settings_shutdown_timeout": "শেয়ার বন্ধ করুন:", + "settings_error_unknown": "টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারে না কারণ আপনার বিন্যাসনসমূহ বোধগম্য নয় । ", + "settings_error_automatic": "টর নিয়ন্ত্রকের সাথে সংযোগ স্থাপন করা যায়নি । টর ব্রাউজার (torproject.org থেকে পাওয়া যায়) ব্রাকগ্রাউন চলমান?", + "settings_error_socket_port": "{}: {} এ টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারছি না । ", + "settings_error_socket_file": "সকেট ফাইল {} ব্যবহার করে টর নিয়ন্ত্রকের সাথে সংযোগ করতে পারে না । ", + "settings_error_auth": "{}: {}-এর সাথে সংযুক্ত, কিন্তু পরীক্ষা করা যাচ্ছে না । হয়তো এটা কোন টর নিয়ন্ত্রক নয়? ", + "settings_error_missing_password": "টর কন্ট্রোলার সাথে সংযুক্ত, কিন্তু তা প্রমাণীকরণ একটি পাসওয়ার্ড প্রয়োজন.", + "settings_error_unreadable_cookie_file": "টর নিয়ন্ত্রকের সাথে সংযুক্ত, কিন্তু পাসওয়ার্ড ভুল হতে পারে, অথবা আপনার ব্যবহারকারীকে কুকি ফাইলে পড়ার অনুমতি দেওয়া হয়নি। ", + "settings_error_bundled_tor_not_supported": "OnionShare এর সাথে আসা টর সংস্করণটি ব্যবহার করে উইন্ডোজ বা ম্যাকোসে ডেভেলপার মোডে কাজ করে না।", + "settings_error_bundled_tor_timeout": "টর সাথে সংযোগ করার জন্য খুব বেশি সময় লাগছে। হয়তো আপনি ইন্টারনেটের সাথে সংযুক্ত নন, অথবা একটি ভুল সিস্টেম ঘড়ি আছে?", + "settings_error_bundled_tor_broken": "ব্যাকগ্রাউন্ডে OnionShare টর এর সাথে সংযুক্ত নয়:\n\n\n{}", + "settings_test_success": "টর কন্ট্রোলার এর সঙ্গে যুক্ত হয়েছে ।\n\nটর সংস্করণ: {}\n\nOnion Services সেবা সমর্থন করে: {}.\n\nক্লায়েন্ট প্রমাণীকরণ সমর্থন করে: {}.\n\nnext-gen .onion ঠিকানাগুলো সমর্থন করে: {} । ", + "error_tor_protocol_error": "টর-এ একটি ত্রুটি ছিল: {} ", + "error_tor_protocol_error_unknown": "টর-এ একটি অজানা ত্রুটি আছে", + "error_invalid_private_key": "এই ব্যক্তিগত কী ধরন টি অসমর্থিত ", + "connecting_to_tor": "টর নেটওয়ার্কে সংযুক্ত হচ্ছে ", "update_available": "", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", + "update_error_check_error": "নতুন সংস্করণের জন্য পরীক্ষা করা যায়নি: onionshare ওয়েবসাইট বলছে সাম্প্রতিক সংস্করণটি হচ্ছে অস্বীকৃত ' {} '...", + "update_error_invalid_latest_version": "নতুন সংস্করণের জন্য পরীক্ষা করা যায়নি: হয়তো আপনি টর-এর সাথে সংযুক্ত নন, অথবা OnionShare ওয়েবসাইট বন্ধ আছে?", + "update_not_available": "আপনি সর্বশেষ OnionShare চালাচ্ছেন ।", + "gui_tor_connection_ask": "টর থেকে সংযোগ সাজাতে সেটিংস খুলুন?", "gui_tor_connection_ask_open_settings": "হ্যাঁ", "gui_tor_connection_ask_quit": "প্রস্থান করুন", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", + "gui_tor_connection_error_settings": " কিভাবে onionshare সেটিংসে টর নেটওয়ার্ক সংযোগ করে পরিবর্তন করতে চেষ্টা করুন ।", + "gui_tor_connection_canceled": "টর-এ সংযোগ করা যায়নি ।\n\nআপনি ইন্টারনেটের সাথে সংযুক্ত আছেন কিনা তা নিশ্চিত করুন, তারপর onionshare পুনরায় খুলুন এবং টর এর সংযোগটি সেট আপ করুন । ", + "gui_tor_connection_lost": "টর থেকে বিচ্ছিন্ন । ", + "gui_server_started_after_timeout": "সার্ভার শুরু হওয়ার আগেই অটো স্টপ টাইমার শেষ হয়ে যায় ।\n\nঅনুগ্রহ করে একটি নতুন শেয়ার তৈরি করুন. ", + "gui_server_timeout_expired": "অটো-স্টপ টাইমার ইতিমধ্যেই শেষ হয়ে গিয়েছে ।\n\nঅনুগ্রহ করে শেয়ারিং শুরু করতে এটি আপডেট করুন. ", + "share_via_onionshare": "এটি OnionShare ", + "gui_use_legacy_v2_onions_checkbox": "লিগ্যাসি ঠিকানাগুলি ব্যবহার করুন ", + "gui_save_private_key_checkbox": "একটি অবিরাম ঠিকানা ব্যবহার করুন ", "gui_share_url_description": "", "gui_receive_url_description": "", "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", + "gui_url_label_stay_open": "এই শেয়ারটি অটো-স্টপ হবে না । ", + "gui_url_label_onetime": "এই শেয়ারটি প্রথম সমাপ্তির পরে বন্ধ হবে. ", "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "gui_status_indicator_share_stopped": "শেয়ার করার জন্য প্রস্তুত ", + "gui_status_indicator_share_working": "শুরু...", + "gui_status_indicator_share_started": "শেয়ারিং", + "gui_status_indicator_receive_stopped": "পাওয়ার জন্য প্রস্তুত ", + "gui_status_indicator_receive_working": "শুরু... ", + "gui_status_indicator_receive_started": "গ্রহণ", + "gui_file_info": "{} ফাইল, {}", + "gui_file_info_single": "{} ফাইল, {}", + "history_in_progress_tooltip": "{} অগ্রসর হচ্ছে ", + "history_completed_tooltip": "{} সম্পূর্ণ\n", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "সতর্কীকরণ: প্রাপ্ত মোড লোকজনকে আপনার কম্পিউটারে ফাইল আপলোড করতে দেয় । আপনি যদি তাদের খোলেন তবে কিছু ফাইল সম্ভবত আপনার কম্পিউটারের নিয়ন্ত্রণ নিতে পারে । শুধুমাত্র আপনি যে ব্যক্তিদের বিশ্বাস করেন, অথবা আপনি যদি জানেন আপনি কি করছেন তা শুধুমাত্র খোলা জিনিস । ", + "gui_receive_mode_warning": "গ্রহণ মোডে লোকজন আপনার কম্পিউটারে ফাইলগুলো আপলোড করতে দেয় ।

    আপনি যদি তাদের খোলেন তবে কিছু ফাইল সম্ভবত আপনার কম্পিউটারের নিয়ন্ত্রণ নিতে পারে । শুধুমাত্র আপনি যে ব্যক্তিদের বিশ্বাস করেন, অথবা আপনি যদি জানেন আপনি কি করছেন তা শুধুমাত্র খোলা জিনিস ।", + "receive_mode_upload_starting": "মোট আকারের {} টি আপলোড শুরু হচ্ছে ", + "receive_mode_received_file": "প্রাপ্ত: {} ", + "gui_mode_share_button": "ফাইলগুলো শেয়ার করুন ", + "gui_mode_receive_button": "ফাইল গ্রহণ করা হচ্ছে ", + "gui_settings_receiving_label": "সেটিংস গ্রহণ করা হচ্ছে ", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "দেখা", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "সর্বজনীন মোড ", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "পৃষ্ঠা লোড করা হয়েছে ", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,7 +179,26 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "" + "gui_open_folder_error_nautilus": "ফোল্ডার খোলা যাচ্ছে না কারণ nautilus বিদ্যমান নয় । ফাইলটি এখানে: {} ", + "gui_settings_language_label": "পছন্দনীয় ভাষা ", + "gui_settings_language_changed_notice": "আপনার ভাষার পরিবর্তন প্রভাব বিস্তার করার জন্য OnionShare পুনর্সূচনা করুন. ", + "gui_add_files": "ফাইল যোগ করো", + "gui_add_folder": "ফোল্ডার যোগ করো", + "gui_settings_onion_label": "Onion সেটিংস", + "gui_connect_to_tor_for_onion_settings": "onion সেবা সেটিংস দেখতে টর এর সাথে সংযোগ করুন ", + "error_cannot_create_data_dir": "onionshare ডাটা ফোল্ডার তৈরি করা যায়নি: {} ", + "receive_mode_data_dir": "আপনার কাছে পাঠানো ফাইলসমূহ এই ফোল্ডারে প্রদর্শিত হয়েছে: {} ", + "gui_settings_data_dir_label": "ফাইল সংরক্ষণ করুন ", + "gui_settings_data_dir_browse_button": "ব্রাউজ", + "systray_page_loaded_message": "onionshare ঠিকানা লোড করা হয়েছে ", + "systray_share_started_title": "শেয়ারিং শুরু করা হয়েছে", + "systray_share_completed_title": "শেয়ারিং সম্পূর্ণ হয়েছে", + "systray_share_completed_message": "ফাইল পাঠানো শেষ হয়েছে", + "systray_share_canceled_title": "শেয়ারকরণ বাতিল করা হয়েছে", + "systray_share_canceled_message": "কেউ আপনার ফাইল গ্রহণ করা বাতিল করেছে", + "systray_receive_started_title": "গ্রহণ শুরু হয়েছে", + "systray_receive_started_message": "কেউ আপনার কাছে ফাইল পাঠাচ্ছে", + "gui_all_modes_history": "ইতিহাস", + "gui_all_modes_clear_history": "সব পরিষ্কার করুন", + "gui_all_modes_transfer_started": "{} শুরু হয়েছে" } diff --git a/share/locale/ca.json b/share/locale/ca.json index 068ed541..a59dae2a 100644 --- a/share/locale/ca.json +++ b/share/locale/ca.json @@ -2,7 +2,7 @@ "config_onion_service": "S'està establint el servei onion al port {0:d}.", "preparing_files": "S'estan comprimint els arxius.", "give_this_url": "Dóna aquesta adreça a la persona destinatària:", - "give_this_url_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona destinatària:", + "give_this_url_stealth": "Fes arribar aquestes dues línies a la/es persona/es destinatària/es:", "give_this_url_receive": "Dóna aquesta adreça a la persona remitent:", "give_this_url_receive_stealth": "Dóna aquesta adreça i la línia HidServAuth a la persona remitent:", "ctrlc_to_stop": "Prem Control+C per aturar el servidor", @@ -11,7 +11,7 @@ "no_available_port": "No s'ha pogut trobar un port disponible per començar el servei onion", "other_page_loaded": "Adreça carregada", "close_on_timeout": "S'ha aturat perquè s'ha acabat el temps d'espera", - "closing_automatically": "S'ha aturat perquè ha acabat la descàrrega", + "closing_automatically": "S'ha aturat perquè ha acabat la transferència", "timeout_download_still_running": "S'està esperant que acabi la descàrrega", "large_filesize": "Compte: La transferència d'arxius molt grans podria trigar hores", "systray_menu_exit": "Surt", @@ -24,7 +24,7 @@ "systray_upload_started_title": "S'ha iniciat la pujada", "systray_upload_started_message": "Algú ha començat a pujar arxius al teu ordinador", "help_local_only": "No facis servir Tor (només per a desenvolupament)", - "help_stay_open": "Manté obert el servei després de la primera descàrrega", + "help_stay_open": "Mantingues obert el servei després d'enviar els arxius", "help_shutdown_timeout": "Deixa de compartir al cap de tants segons", "help_stealth": "Fes servir autorització de client (avançat)", "help_receive": "Rep recursos en comptes d'enviar-los", @@ -68,8 +68,8 @@ "error_ephemeral_not_supported": "OnionShare necessita almenys les versions Tor 0.2.7.1 i python3-stem 1.4.0.", "gui_settings_window_title": "Configuració", "gui_settings_whats_this": "Què és això?", - "gui_settings_stealth_option": "Fes servir autorització de client (antiquada)", - "gui_settings_stealth_hidservauth_string": "Ara que has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu HidServAuth.", + "gui_settings_stealth_option": "Fes servir autorització de client", + "gui_settings_stealth_hidservauth_string": "Ara que ja has desat la clau privada per reutilitzar-la,\nja pots clicar per copiar el teu \"HidServAuth\".", "gui_settings_autoupdate_label": "Comprova si hi ha noves versions", "gui_settings_autoupdate_option": "Notifica'm si hi ha una actualització disponible", "gui_settings_autoupdate_timestamp": "Última comprovació: {}", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Comprova si hi ha una versió més nova", "gui_settings_general_label": "Configuració general", "gui_settings_sharing_label": "Configuració de compartir", - "gui_settings_close_after_first_download_option": "Deixa de compartir després de la primera descàrrega", + "gui_settings_close_after_first_download_option": "Deixa de compartir després d'enviar arxius", "gui_settings_connection_type_label": "Com hauria de connectar-se OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Fes servir la versió de Tor inclosa dins d'OnionShare", "gui_settings_connection_type_automatic_option": "Intenta la configuració automàtica amb el Navegador Tor", @@ -122,54 +122,54 @@ "error_invalid_private_key": "Aquest tipus de clau privada no està suportat", "connecting_to_tor": "Connectant a la xarxa Tor", "update_available": "Ha sortit una nova versió d'OnionShare.Feu clic aquí per obtenir-la.

    Esteu usant {} i la més recent és {}.", - "update_error_check_error": "", - "update_error_invalid_latest_version": "", - "update_not_available": "", - "gui_tor_connection_ask": "", - "gui_tor_connection_ask_open_settings": "", - "gui_tor_connection_ask_quit": "", - "gui_tor_connection_error_settings": "", - "gui_tor_connection_canceled": "", - "gui_tor_connection_lost": "", - "gui_server_started_after_timeout": "", - "gui_server_timeout_expired": "", - "share_via_onionshare": "", - "gui_use_legacy_v2_onions_checkbox": "", - "gui_save_private_key_checkbox": "", - "gui_share_url_description": "", - "gui_receive_url_description": "", - "gui_url_label_persistent": "", - "gui_url_label_stay_open": "", - "gui_url_label_onetime": "", - "gui_url_label_onetime_and_persistent": "", - "gui_status_indicator_share_stopped": "", - "gui_status_indicator_share_working": "", - "gui_status_indicator_share_started": "", - "gui_status_indicator_receive_stopped": "", - "gui_status_indicator_receive_working": "", - "gui_status_indicator_receive_started": "", - "gui_file_info": "", - "gui_file_info_single": "", - "history_in_progress_tooltip": "", - "history_completed_tooltip": "", + "update_error_check_error": "No s'ha pogut comprovar si hi ha versions més noves. La web d'OnionShare diu que l'última versió és '{}' però no s'ha pogut reconèixer…", + "update_error_invalid_latest_version": "No s'ha pogut comprovar si hi ha una versió més nova. Pot ser que no estiguis connectat/da a Tor o que la web d'OnionShare estigui caiguda?", + "update_not_available": "Aquesta és la versió més nova d'OnionShare.", + "gui_tor_connection_ask": "Vols anar a la configuració per provar d'arreglar la connexió a Tor?", + "gui_tor_connection_ask_open_settings": "Sí", + "gui_tor_connection_ask_quit": "Surt", + "gui_tor_connection_error_settings": "Prova de canviar la configuració de com OnionShare es connecta a la xarxa Tor.", + "gui_tor_connection_canceled": "No s'ha pogut establir la connexió amb la xarxa Tor.\n\nAssegura't que tens connexió a internet, torna a obrir OnionShare i prepara la connexió a Tor.", + "gui_tor_connection_lost": "S'ha perdut la connexió amb Tor.", + "gui_server_started_after_timeout": "El temporitzador ha acabat abans que s'iniciés el servidor.\nTorna a compartir-ho.", + "gui_server_timeout_expired": "El temporitzador ja s'ha acabat.\nReinicia'l per a poder compartir.", + "share_via_onionshare": "Comparteix-ho amb OnionShare", + "gui_use_legacy_v2_onions_checkbox": "Fes servir adreces amb un format antic", + "gui_save_private_key_checkbox": "Fes servir una adreça persistent", + "gui_share_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot descarregar arxius teus fent servir el Navegador de Tor: ", + "gui_receive_url_description": "Qualsevol persona amb aquesta adreça d'OnionShare pot pujar arxius al teu ordinador fent servir el Navegador de Tor: ", + "gui_url_label_persistent": "Aquesta sessió no es tancarà.

    Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».", + "gui_url_label_stay_open": "Aquesta recurs no es deixarà de compartir sol.", + "gui_url_label_onetime": "Aquest recurs deixarà de compartir-se després de la primera descàrrega.", + "gui_url_label_onetime_and_persistent": "Aquest recurs no es deixarà de compartir sol.

    Cada recurs compartit reutilitzarà aquesta mateixa adreça. Si vols crear una adreça diferent per a cada recurs, desactiva l'opció «Fes servir una adreça persistent».", + "gui_status_indicator_share_stopped": "A punt per compartir", + "gui_status_indicator_share_working": "S'està iniciant…", + "gui_status_indicator_share_started": "S'està compartint", + "gui_status_indicator_receive_stopped": "A punt per rebre", + "gui_status_indicator_receive_working": "S'està iniciant…", + "gui_status_indicator_receive_started": "S'està rebent", + "gui_file_info": "{} arxius, {}", + "gui_file_info_single": "{} arxiu, {}", + "history_in_progress_tooltip": "{} en procés", + "history_completed_tooltip": "{} completat/s", "info_in_progress_uploads_tooltip": "", "info_completed_uploads_tooltip": "", "error_cannot_create_downloads_dir": "", "receive_mode_downloads_dir": "", - "receive_mode_warning": "", - "gui_receive_mode_warning": "", - "receive_mode_upload_starting": "", - "receive_mode_received_file": "", - "gui_mode_share_button": "", - "gui_mode_receive_button": "", - "gui_settings_receiving_label": "", + "receive_mode_warning": "Alerta: El mode de rebuda permet a qualsevol de pujar arxius al teu ordinador. Algú amb males intencions podria pendre el control de la teva màquina si obrissis arxius maliciosos que haguessin pujat. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.", + "gui_receive_mode_warning": "El mode de rebuda permet a qualsevol de pujar arxius al teu ordinador.

    Algú amb males intencions podria pendre el control de la teva màquina si et pugessin i obrissis arxius maliciosos. Obre només arxius de persones que confiïs si no saps com evitar aquests riscos.", + "receive_mode_upload_starting": "S'està començant a rebre {}", + "receive_mode_received_file": "S'han rebut: {}", + "gui_mode_share_button": "Comparteix arxius", + "gui_mode_receive_button": "Rep arxius", + "gui_settings_receiving_label": "Configuració de rebuda", "gui_settings_downloads_label": "", "gui_settings_downloads_button": "", "gui_settings_receive_allow_receiver_shutdown_checkbox": "", - "gui_settings_public_mode_checkbox": "", + "gui_settings_public_mode_checkbox": "Mode públic", "systray_close_server_title": "", "systray_close_server_message": "", - "systray_page_loaded_title": "", + "systray_page_loaded_title": "S'ha carregat la pàgina", "systray_download_page_loaded_message": "", "systray_upload_page_loaded_message": "", "gui_uploads": "", @@ -179,8 +179,39 @@ "gui_upload_finished_range": "", "gui_upload_finished": "", "gui_download_in_progress": "", - "gui_open_folder_error_nautilus": "", - "gui_settings_language_label": "", - "gui_settings_language_changed_notice": "", - "timeout_upload_still_running": "S'està esperant que acabi la pujada" + "gui_open_folder_error_nautilus": "No s'ha pogut obrir la carpeta perquè el Nautilus no està disponible. L'arxiu està a: {}", + "gui_settings_language_label": "Llengua preferida", + "gui_settings_language_changed_notice": "Reobre OnionShare perquè el canvi de llengua tingui efecte.", + "timeout_upload_still_running": "S'està esperant que acabi la pujada", + "gui_add_files": "Afegeix arxius", + "gui_add_folder": "Afegeix una carpeta", + "gui_settings_onion_label": "Servei ceba", + "gui_connect_to_tor_for_onion_settings": "Connecta't a Tor per configurar els serveis ocults", + "error_cannot_create_data_dir": "No s'ha pogut crear la carpeta de dades d'OnionShare: {}", + "receive_mode_data_dir": "Els arxius que rebis apareixeran aquí: {}", + "gui_settings_data_dir_label": "Desa els arxius a", + "gui_settings_data_dir_browse_button": "Explora", + "systray_page_loaded_message": "L'adreça d'OnionShare s'ha carregat", + "systray_share_started_title": "S'ha començat a compartir", + "systray_share_started_message": "S'està començant a enviar els arxius a algú", + "systray_share_completed_title": "S'ha acabat de compartir", + "systray_share_completed_message": "Els arxius s'han acabat d'enviar", + "systray_share_canceled_title": "S'ha deixat de compartir", + "systray_share_canceled_message": "Algú ha aturat la descàrrega que estava fent dels teus fitxers", + "systray_receive_started_title": "S'ha començat a rebre", + "systray_receive_started_message": "Algú t'està enviant arxius", + "gui_all_modes_history": "Historial", + "gui_all_modes_clear_history": "Esborra-ho tot", + "gui_all_modes_transfer_started": "Ha començat a {}", + "gui_all_modes_transfer_finished_range": "S'ha transferit entre {} i {}", + "gui_all_modes_transfer_finished": "Transferit a {}", + "gui_all_modes_transfer_canceled_range": "S'ha canceŀlat. Ha funcionat entre {} i {}", + "gui_all_modes_transfer_canceled": "S'ha canceŀlat {}", + "gui_all_modes_progress_complete": "Han passat %p%, {0:s}.", + "gui_all_modes_progress_starting": "{0:s}, %p% (s'està calculant)", + "gui_all_modes_progress_eta": "{0:s}, Temps aproximat: {1:s}, %p%", + "gui_share_mode_no_files": "Encara no s'han enviat fitxers", + "gui_share_mode_timeout_waiting": "S'està acabant d'enviar", + "gui_receive_mode_no_files": "Encara no s'ha rebut res", + "gui_receive_mode_timeout_waiting": "S'està acabant de rebre" } diff --git a/share/locale/da.json b/share/locale/da.json index b87f0151..39edcf22 100644 --- a/share/locale/da.json +++ b/share/locale/da.json @@ -52,7 +52,7 @@ "error_ephemeral_not_supported": "OnionShare kræver mindst både Tor 0.2.7.1 og python3-stem 1.4.0.", "gui_settings_window_title": "Indstillinger", "gui_settings_stealth_option": "Brug klientautentifikation", - "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu\nkan klikke for at kopiere din HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Ved at have gemt din private nøgle til at blive brugt igen, betyder det at du nu kan klikke for at kopiere din HidServAuth.", "gui_settings_autoupdate_label": "Søg efter ny version", "gui_settings_autoupdate_option": "Giv mig besked når der findes en ny version", "gui_settings_autoupdate_timestamp": "Sidste søgning: {}", @@ -215,5 +215,6 @@ "gui_receive_mode_no_files": "Der er endnu ikke modtaget nogen filer", "gui_receive_mode_timeout_waiting": "Venter på at blive færdig med at modtage", "gui_all_modes_transfer_canceled_range": "Annullerede {} - {}", - "gui_all_modes_transfer_canceled": "Annullerede {}" + "gui_all_modes_transfer_canceled": "Annullerede {}", + "gui_settings_onion_label": "Onion-indstillinger" } diff --git a/share/locale/de.json b/share/locale/de.json index 8e9bd634..20ad9fe4 100644 --- a/share/locale/de.json +++ b/share/locale/de.json @@ -4,9 +4,9 @@ "ctrlc_to_stop": "Drücke Strg+C um den Server anzuhalten", "not_a_file": "{0:s} ist keine gültige Datei.", "other_page_loaded": "URL geladen", - "closing_automatically": "Gestoppt, da der Download beendet wurde", - "large_filesize": "Warnung: Das Senden von großen Dateien kann Stunden dauern", - "help_local_only": "Tor nicht benutzen (nur für Entwicklung)", + "closing_automatically": "Gestoppt, da der Download erfolgreich beendet wurde", + "large_filesize": "Warnung: Das Hochladen von großen Dateien kann Stunden dauern", + "help_local_only": "Tor nicht verwenden (nur für Entwicklung)", "help_stay_open": "Den OnionService nicht anhalten nachdem ein Download beendet wurde", "help_debug": "Schreibe Fehler von OnionShare nach stdout und Webfehler auf die Festplatte", "help_filename": "Liste der zu teilenden Dateien oder Ordner", @@ -50,10 +50,10 @@ "give_this_url_receive_stealth": "Gib diese URL und die HidServAuth-Zeile an den Sender:", "not_a_readable_file": "{0:s} kann nicht gelesen werden.", "no_available_port": "Es konnte kein freier Port gefunden werden, um den Onionservice zu starten", - "close_on_timeout": "Wegen Zeitablaufs gestoppt", + "close_on_timeout": "Angehalten da der auto-stop Timer abgelaufen ist", "systray_upload_started_title": "OnionShare Upload wurde gestartet", "systray_upload_started_message": "Ein Benutzer hat begonnen, Dateien auf deinen Computer hochzuladen", - "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten", + "help_shutdown_timeout": "Den Server nach einer bestimmten Zeit anhalten (in Sekunden)", "help_receive": "Empfange Dateien anstatt sie zu senden", "gui_share_stop_server_shutdown_timeout": "Server stoppen (läuft noch {} Sekunden)", "gui_share_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", @@ -77,10 +77,10 @@ "gui_tor_connection_ask_quit": "Beenden", "gui_tor_connection_lost": "Verbindung zu Tor getrennt.", "help_stealth": "Nutze Klientauthorisierung (fortgeschritten)", - "gui_receive_start_server": "Starte den Empfängermodus", - "gui_receive_stop_server": "Stoppe den Empfängermodus", - "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {})", - "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} Sekunden ab", + "gui_receive_start_server": "Starte den Empfangsmodus", + "gui_receive_stop_server": "Stoppe den Empfangsmodus", + "gui_receive_stop_server_shutdown_timeout": "Stoppe den Empfängermodus (stoppt automatisch in {} Sekunden)", + "gui_receive_stop_server_shutdown_timeout_tooltip": "Zeit läuft in {} ab", "gui_no_downloads": "Bisher keine Downloads", "gui_copied_url_title": "OnionShare-Adresse kopiert", "gui_copied_hidservauth": "HidServAuth-Zeile in die Zwischenablage kopiert", @@ -91,9 +91,9 @@ "gui_quit_title": "Nicht so schnell", "gui_share_quit_warning": "Du versendest gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", "gui_receive_quit_warning": "Du empfängst gerade Dateien. Bist du sicher, dass du OnionShare beenden willst?", - "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu verschicken.", - "zip_progress_bar_format": "Komprimiere: %p%", - "error_stealth_not_supported": "Um Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.", + "error_rate_limit": "Jemand hat deine Adresse zu oft falsch eingegeben, das heißt, jemand könnte versuchen, sie zu erraten. Deswegen hat OnionShare den Server gestoppt. Starte den Server erneut und schicke dem Empfänger die neue Adresse, um die Dateien zu versenden.", + "zip_progress_bar_format": "Komprimierung: %p%", + "error_stealth_not_supported": "Um die Klientauthorisierung zu nutzen, benötigst du mindestens Tor 0.2.9.1-alpha (oder Tor Browser 6.5) und python3-stem 1.5.0.", "error_ephemeral_not_supported": "OnionShare benötigt mindestens Tor 0.2.7.1 als auch python3-stem 1.4.0.", "gui_settings_whats_this": "Was ist das?", "gui_settings_stealth_option": "Nutze Klientauthorisierung", @@ -115,7 +115,7 @@ "settings_error_bundled_tor_not_supported": "Im Entwicklermodus auf Windows oder macOS kannst du die Torversion, die mit OnionShare geliefert wird, nicht nutzen.", "settings_error_bundled_tor_timeout": "Die Verbindung zum Tornetzwerk braucht zu lang. Bist du vielleicht nicht mit dem Internet verbunden oder geht die Uhr auf deinem System falsch?", "settings_error_bundled_tor_broken": "OnionShare konnte im Hintergrund nicht mit Tor verbinden:\n{}", - "settings_test_success": "Verbunden mit dem Tor controller.\n\nTorversion: {}\nUnterstützt flüchtige onion services: {}.\nUnterstützt Klientauthorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", + "settings_test_success": "Verbunden mit dem Tor controller.\n\nTor-Version: {}\nUnterstützt vorübergehende onion services: {}.\nUnterstützt Client-Authorisierung: {}.\nUnterstützt .onion-Adressen der nächsten Generation: {}.", "error_tor_protocol_error": "Es gab einen Fehler mit Tor: {}", "error_tor_protocol_error_unknown": "Es gab einen unbekannten Fehler mit Tor", "error_invalid_private_key": "Diese Art von privatem Schlüssel wird nicht unterstützt", @@ -179,11 +179,23 @@ "gui_status_indicator_share_stopped": "Bereit zum teilen", "history_in_progress_tooltip": "{} läuft", "receive_mode_upload_starting": "Hochladen von insgesamt {} beginnt", - "systray_page_loaded_title": "OnionShare Seite geladen", + "systray_page_loaded_title": "Seite geladen", "gui_upload_finished_range": "{} hochgeladen zu {}", "gui_upload_finished": "{} hochgeladen", "gui_add_files": "Dateien hinzufügen", "gui_add_folder": "Ordner hinzufügen", "gui_connect_to_tor_for_onion_settings": "Verbinde dich mit Tor, um die Einstellungen für onion services zu sehen", - "gui_url_label_onetime_and_persistent": "Dieser Server wird nicht automatisch stoppen. >br>
    Jeder nachfolgende Server wird die gleiche Adresse nutzen. (Um jedes mal eine andere Adresse zu nutzen, schalte \"Nutze eine gleichbleibende Adresse\" in den Einstellungen aus.)" + "gui_url_label_onetime_and_persistent": "Dieser Server wird nicht automatisch stoppen. >br>
    Jeder nachfolgende Server wird die gleiche Adresse nutzen. (Um jedes mal eine andere Adresse zu nutzen, schalte \"Nutze eine gleichbleibende Adresse\" in den Einstellungen aus.)", + "gui_settings_onion_label": "Adresseinstellungen", + "error_cannot_create_data_dir": "Der Ordner für die OnionSharedateien konnte nicht erstellt werden: {}", + "receive_mode_data_dir": "Die Dateien, die dir geschickt wurden, findest du in folgendem Ordner: {}", + "gui_settings_data_dir_label": "Speichere Dateien in", + "gui_settings_data_dir_browse_button": "Durchsuchen", + "systray_page_loaded_message": "OnionShare-Adresse geladen", + "systray_share_started_title": "Freigabe gestartet", + "systray_share_started_message": "Upload von Dateien begonnen", + "systray_share_completed_title": "Freigabe erfolgt", + "systray_share_completed_message": "Dateien erfolgreich versandt", + "systray_share_canceled_title": "Freigabe abgebrochen", + "systray_share_canceled_message": "Jemand hat den Download deiner Dateien abgebrochen" } diff --git a/share/locale/el.json b/share/locale/el.json index c217716b..4157c592 100644 --- a/share/locale/el.json +++ b/share/locale/el.json @@ -69,7 +69,7 @@ "gui_settings_window_title": "Ρυθμίσεις", "gui_settings_whats_this": " Τί είναι αυτό? ", "gui_settings_stealth_option": "Χρήση εξουσιοδότηση πελάτη", - "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα \nνα επιλέξετε την αντιγραφή του HidServAuth σας.", + "gui_settings_stealth_hidservauth_string": "Με την αποθήκευση των κλειδιών σας για χρήση εκ νέου, μπορείτε τώρα να επιλέξετε την αντιγραφή του HidServAuth σας.", "gui_settings_autoupdate_label": "Έλεγχος για νέα έκδοση", "gui_settings_autoupdate_option": "Ενημερώστε με όταν είναι διαθέσιμη μια νέα έκδοση", "gui_settings_autoupdate_timestamp": "Τελευταίος έλεγχος: {}", @@ -210,5 +210,8 @@ "gui_share_mode_no_files": "Δεν Στάλθηκαν Αρχεία Ακόμα", "gui_share_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση αποστολής", "gui_receive_mode_no_files": "Δεν Εγινε Καμμία Λήψη Αρχείων Ακόμα", - "gui_receive_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση της λήψης" + "gui_receive_mode_timeout_waiting": "Αναμένοντας την ολοκλήρωση της λήψης", + "gui_settings_onion_label": "Ρυθμίσεις Onion", + "gui_all_modes_transfer_canceled_range": "Ακυρώθηκε {} - {}", + "gui_all_modes_transfer_canceled": "Ακυρώθηκε {}" } diff --git a/share/locale/es.json b/share/locale/es.json index 3c6452a8..9ad6fad5 100644 --- a/share/locale/es.json +++ b/share/locale/es.json @@ -37,7 +37,7 @@ "error_ephemeral_not_supported": "OnionShare requiere ambos Tor 0.2.7.1 y python3-stem 1.4.0 al menos.", "gui_settings_window_title": "Configuración", "gui_settings_stealth_option": "Utilizar autorización de cliente", - "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes\nhacer clic para copiar tu HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Habiendo guardado tu clave privada para volver a utilizarla, ahora puedes hacer clic para copiar tu HidServAuth.", "gui_settings_autoupdate_label": "Comprobar si hay una versión nueva", "gui_settings_autoupdate_option": "Notificarme cuando haya una nueva versión disponible", "gui_settings_autoupdate_check_button": "Comprobar por una Nueva Versión", @@ -216,5 +216,6 @@ "gui_receive_mode_no_files": "No se recibieron archivos todavía", "gui_receive_mode_timeout_waiting": "Esperando a que termine la recepción", "gui_all_modes_transfer_canceled_range": "Cancelado {} - {}", - "gui_all_modes_transfer_canceled": "Cancelado {}" + "gui_all_modes_transfer_canceled": "Cancelado {}", + "gui_settings_onion_label": "Configuración de Onion" } diff --git a/share/locale/fa.json b/share/locale/fa.json index eafa64c1..7e6c305c 100644 --- a/share/locale/fa.json +++ b/share/locale/fa.json @@ -69,7 +69,7 @@ "gui_settings_window_title": "تنظیمات", "gui_settings_whats_this": "این چیست؟", "gui_settings_stealth_option": "استفاده از احراز هویت کلاینت", - "gui_settings_stealth_hidservauth_string": "ذخیره کردن کلید خصوصی برای استفاده دوباره، بدین معناست که الان می توانید\nبرای کپی HidServAuth کلیک کنید.", + "gui_settings_stealth_hidservauth_string": "ذخیره کردن کلید خصوصی برای استفاده دوباره، بدین معناست که الان می توانید برای کپی HidServAuth کلیک کنید.", "gui_settings_autoupdate_label": "بررسی برای نسخه جدید", "gui_settings_autoupdate_option": "زمانی که نسخه جدید موجود بود من را خبر کن", "gui_settings_autoupdate_timestamp": "آخرین بررسی: {}", diff --git a/share/locale/fi.json b/share/locale/fi.json index d0ee80ff..7f31450f 100644 --- a/share/locale/fi.json +++ b/share/locale/fi.json @@ -1,7 +1,7 @@ { "preparing_files": "Valmistellaan tiedostoja jaettavaksi.", - "give_this_url": "Anna tämä URL-osoite henkilölle, jolle lähetät tiedostot:", - "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", + "give_this_url": "Anna tämä URL-osoite vastaanottajalle:", + "ctrlc_to_stop": "Näppäin Ctrl-C pysäyttää palvelimen", "not_a_file": "{0:s} Ei ole tiedosto.", "other_page_loaded": "URL-osoite ladattu", "closing_automatically": "Lataus valmis. Suljetaan automaattisesti", diff --git a/share/locale/fr.json b/share/locale/fr.json index 6405362b..8d87a501 100644 --- a/share/locale/fr.json +++ b/share/locale/fr.json @@ -21,16 +21,16 @@ "gui_choose_items": "Sélectionner", "gui_share_start_server": "Commencer le partage", "gui_share_stop_server": "Arrêter le partage", - "gui_copy_url": "Copier l'adresse", + "gui_copy_url": "Copier l’adresse", "gui_copy_hidservauth": "Copier HidServAuth", "gui_downloads": "Historique de téléchargement", "gui_canceled": "Annulé", - "gui_copied_url": "Adresse OnionShare copiée dans le presse-papiers", + "gui_copied_url": "L’adresse OnionShare a été copiée dans le presse-papiers", "gui_please_wait": "Démarrage… Cliquez pour annuler.", "gui_quit_warning_quit": "Quitter", "gui_quit_warning_dont_quit": "Annuler", "gui_settings_autoupdate_timestamp_never": "Jamais", - "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que votre changement de langue prenne effet.", + "gui_settings_language_changed_notice": "Redémarrez OnionShare pour que la nouvelle langue soit appliquée.", "config_onion_service": "Mise en place du service oignon sur le port {0:d}.", "give_this_url_stealth": "Donnez cette adresse et cette ligne HidServAuth au destinataire :", "give_this_url_receive": "Donnez cette adresse à l’expéditeur :", @@ -38,30 +38,30 @@ "not_a_readable_file": "{0:s} n’est pas un fichier lisible.", "timeout_download_still_running": "En attente de la fin du téléchargement", "systray_download_completed_message": "La personne a terminé de télécharger vos fichiers", - "gui_copied_hidservauth_title": "HidServAuth copié", + "gui_copied_hidservauth_title": "HidServAuth a été copié", "gui_settings_window_title": "Paramètres", - "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", + "gui_settings_autoupdate_timestamp": "Dernière vérification : {}", "gui_settings_close_after_first_download_option": "Arrêter le partage après envoi des fichiers", - "gui_settings_connection_type_label": "Comment OnionShare doit-il se connecter à Tor ?", - "gui_settings_connection_type_control_port_option": "Se connecter avec le port de contrôle", - "gui_settings_connection_type_socket_file_option": "Se connecter à l'aide d'un fichier socket", + "gui_settings_connection_type_label": "Comment OnionShare devrait-il se connecter à Tor ?", + "gui_settings_connection_type_control_port_option": "Se connecter en utilisant le port de contrôle", + "gui_settings_connection_type_socket_file_option": "Se connecter en utilisant un fichier socket", "gui_settings_socket_file_label": "Fichier socket", "gui_settings_socks_label": "Port SOCKS", - "gui_settings_authenticate_no_auth_option": "Pas d'authentification ou authentification par cookie", + "gui_settings_authenticate_no_auth_option": "Pas d’authentification ou authentification par témoin", "gui_settings_authenticate_password_option": "Mot de passe", "gui_settings_password_label": "Mot de passe", "gui_settings_tor_bridges_no_bridges_radio_option": "Ne pas utiliser de pont", - "gui_settings_button_save": "Sauvegarder", + "gui_settings_button_save": "Enregistrer", "gui_settings_button_cancel": "Annuler", "gui_settings_button_help": "Aide", "gui_settings_shutdown_timeout": "Arrêter le partage à :", "connecting_to_tor": "Connexion au réseau Tor", "help_config": "Emplacement du fichier personnalisé de configuration JSON (facultatif)", "large_filesize": "Avertissement : envoyer un gros partage peut prendre des heures", - "gui_copied_hidservauth": "Ligne HidServAuth copiée dans le presse-papiers", + "gui_copied_hidservauth": "La ligne HidServAuth a été copiée dans le presse-papiers", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "zip_progress_bar_format": "Compression : %p%", - "error_ephemeral_not_supported": "OnionShare nécessite au moins Tor 0.2.7.1 et python3-stem 1.4.0.", + "zip_progress_bar_format": "Compression : %p%", + "error_ephemeral_not_supported": "OnionShare exige au moins Tor 0.2.7.1 et python3-stem 1.4.0.", "help_shutdown_timeout": "Arrêter le partage après un certain nombre de secondes", "gui_tor_connection_error_settings": "Essayez de modifier dans les paramètres la façon dont OnionShare se connecte au réseau Tor.", "no_available_port": "Impossible de trouver un port disponible pour démarrer le service oignon", @@ -69,45 +69,45 @@ "systray_upload_started_title": "Envoi OnionShare démarré", "systray_upload_started_message": "Une personne a commencé à envoyer des fichiers vers votre ordinateur", "gui_no_downloads": "Pas encore de téléchargement", - "gui_copied_url_title": "Adresse OnionShare copiée", + "gui_copied_url_title": "L’adresse OnionShare a été copiée", "gui_quit_title": "Pas si vite", - "gui_share_quit_warning": "Vous êtes en train d'envoyer des fichiers. Voulez-vous vraiment quitter OnionShare ?", - "gui_receive_quit_warning": "Vous êtes en train de recevoir des fichiers. Voulez-vous vraiment quitter OnionShare ?", - "gui_settings_whats_this": "Qu'est-ce que c'est ?", - "gui_settings_autoupdate_label": "Rechercher des mises à jour", - "gui_settings_autoupdate_option": "Me notifier lorsque des mises à jour sont disponibles", + "gui_share_quit_warning": "Des fichiers sont en cours d’envoi. Voulez-vous vraiment quitter OnionShare ?", + "gui_receive_quit_warning": "Des fichiers sont en cours de réception. Voulez-vous vraiment quitter OnionShare ?", + "gui_settings_whats_this": "Qu’est-ce que c’est ?", + "gui_settings_autoupdate_label": "Vérifier les nouvelles versions", + "gui_settings_autoupdate_option": "Me signaler toute nouvelle version", "gui_settings_general_label": "Paramètres généraux", "gui_settings_sharing_label": "Paramètres de partage", - "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor inclue dans OnionShare", - "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le navigateur Tor", + "gui_settings_connection_type_bundled_option": "Utiliser la version de Tor intégrée dans OnionShare", + "gui_settings_connection_type_automatic_option": "Essayer la configuration automatique avec le Navigateur Tor", "gui_settings_connection_type_test_button": "Tester la connexion à Tor", "gui_settings_control_port_label": "Port de contrôle", - "gui_settings_authenticate_label": "Paramètres d'authentification de Tor", + "gui_settings_authenticate_label": "Paramètres d’authentification de Tor", "gui_settings_tor_bridges": "Prise en charge des ponts de Tor", "gui_settings_tor_bridges_custom_radio_option": "Utiliser des ponts personnalisés", "gui_settings_tor_bridges_custom_label": "Vous pouvez obtenir des ponts sur https://bridges.torproject.org", "gui_settings_tor_bridges_invalid": "Aucun des ponts que vous avez ajoutés ne fonctionne.\nVérifiez-les de nouveau ou ajoutez-en d’autres.", - "settings_error_unknown": "Impossible de se connecter au contrôleur Tor car les paramètres n'ont pas de sens.", - "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Est-ce que le navigateur Tor (disponible sur torproject.org) fonctionne en arrière-plan ?", + "settings_error_unknown": "Impossible de se connecter au contrôleur Tor, car vos paramètres sont incorrects.", + "settings_error_automatic": "Impossible de se connecter au contrôleur Tor. Le Navigateur Tor (proposé sur torproject.org) fonctionne-t-il en arrière-plan ?", "settings_error_socket_port": "Impossible de se connecter au contrôleur Tor à {}:{}.", "settings_error_socket_file": "Impossible de se connecter au contrôleur Tor en utilisant le fichier socket {}.", - "settings_error_auth": "Connecté à {}:{} mais impossible de s'authentifier. Peut-être que ce n'est pas un contrôleur Tor ?", - "settings_error_missing_password": "Connecté au contrôleur Tor mais il demande un mot de passe pour s'authentifier.", - "settings_error_unreadable_cookie_file": "Connecté au contrôleur Tor, mais le mot de passe est peut-être faux ou l'utilisateur n'a pas la permission de lire le fichier cookie.", - "settings_error_bundled_tor_not_supported": "L'utilisation de la version de Tor inclue avec OnionShare ne marche pas dans le mode développement sous Windows ou macOS.", - "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Peut-être qu'il n'y a pas de connexion à Internet ou que l'horloge système est inexacte ?", - "settings_error_bundled_tor_broken": "OnionShare n'a pas pu se connecter à Tor en arrière-plan :\n{}", - "error_tor_protocol_error": "Il y a eu une erreur avec Tor : {}", - "error_tor_protocol_error_unknown": "Il y a eu une erreur inconnue avec Tor", - "error_invalid_private_key": "Ce type de clé privée n'est pas supporté", - "update_available": "Une nouvelle version de OnionShare est disponible. Cliquez ici pour l'obtenir.

    Vous utilisez actuellement la version {} et la dernière version est la {}.", - "update_not_available": "Vous utilisez la dernière version d'OnionShare.", + "settings_error_auth": "Vous êtes connecté à {}:{}, mais il est impossible de s’authentifier. Est-ce bien un contrôleur Tor ?", + "settings_error_missing_password": "Vous êtes connecté au contrôleur Tor, mais un mot de passe d’authentification est exigé.", + "settings_error_unreadable_cookie_file": "Vous êtes connecté au contrôleur Tor, mais le mot de passe est peut-être erroné ou votre utilisateur n’est pas autorisé à lire le fichier témoin.", + "settings_error_bundled_tor_not_supported": "La version de Tor intégrée dans OnionShare ne fonctionne pas en mode développeur sous Windows ou macOS.", + "settings_error_bundled_tor_timeout": "La connexion à Tor prend trop de temps. Êtes-vous connecté à Internet ? Votre horloge système est-elle mal réglée ?", + "settings_error_bundled_tor_broken": "OnionShare n’a pas réussi à se connecter à Tor en arrière-plan :\n{}", + "error_tor_protocol_error": "Une erreur est survenue avec Tor : {}", + "error_tor_protocol_error_unknown": "Une erreur inconnue est survenue avec Tor", + "error_invalid_private_key": "Ce type de clé privée n’est pas pris en charge", + "update_available": "Une nouvelle version d’OnionShare est proposée. Cliquez ici pour l’obtenir.

    Vous utilisez la version {} et {} est la dernière version.", + "update_not_available": "Vous utilisez la dernière version d’OnionShare.", "gui_tor_connection_ask_open_settings": "Oui", "gui_tor_connection_ask_quit": "Quitter", - "gui_tor_connection_lost": "Déconnecté de Tor.", - "share_via_onionshare": "Partager via OnionShare", + "gui_tor_connection_lost": "Vous êtes déconnecté de Tor.", + "share_via_onionshare": "Partager avec OnionShare", "gui_save_private_key_checkbox": "Utiliser une adresse persistante", - "gui_share_url_description": "Avec cette adresse OnionShare n'importe qui peut télécharger vos fichiers en utilisant le navigateur Tor : ", + "gui_share_url_description": "Quiconque possède cette adresse OnionShare peut télécharger vos fichiers en utilisant le Navigateur Tor : ", "gui_receive_url_description": "Quiconque possède cette adresse OnionShare peut téléverser des fichiers vers votre ordinateur en utilisant le Navigateur Tor : ", "gui_url_label_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", "gui_url_label_stay_open": "Ce partage ne s’arrêtera pas automatiquement.", @@ -115,20 +115,20 @@ "gui_url_label_onetime_and_persistent": "Ce partage ne s’arrêtera pas automatiquement.

    Tout partage subséquent réutilisera l’adresse. (Pour des adresses qui ne peuvent être utilisées qu’une fois, désactivez « Utiliser une adresse persistante » dans les paramètres.)", "gui_status_indicator_share_stopped": "Prêt à partager", "gui_status_indicator_share_working": "Démarrage…", - "gui_status_indicator_share_started": "Partage", + "gui_status_indicator_share_started": "Partage en cours", "gui_status_indicator_receive_stopped": "Prêt à recevoir", "gui_status_indicator_receive_working": "Démarrage…", - "gui_status_indicator_receive_started": "Réception", + "gui_status_indicator_receive_started": "Réception en cours", "gui_file_info": "{} fichiers, {}", "gui_file_info_single": "{} fichier, {}", "history_in_progress_tooltip": "{} en cours", "history_completed_tooltip": "{} terminé", "receive_mode_downloads_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", - "receive_mode_warning": "Avertissement : le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur. Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", + "receive_mode_warning": "Avertissement : Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur. Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", "gui_receive_mode_warning": "Le mode réception permet à d’autres de téléverser des fichiers vers votre ordinateur.

    Certains fichiers pourraient prendre le contrôle de votre ordinateur si vous les ouvrez. N’ouvrez que des fichiers provenant de personnes de confiance ou si vous savez ce que vous faites.", - "receive_mode_received_file": "Reçu : {}", - "gui_mode_share_button": "Fichiers partagés", - "gui_mode_receive_button": "Fichiers reçus", + "receive_mode_received_file": "Reçu : {}", + "gui_mode_share_button": "Partager des fichiers", + "gui_mode_receive_button": "Recevoir des fichiers", "gui_settings_receiving_label": "Paramètres de réception", "gui_settings_downloads_label": "Enregistrer les fichiers sous", "gui_settings_downloads_button": "Parcourir", @@ -142,7 +142,7 @@ "gui_upload_finished_range": "Envoyé {} de {}", "gui_upload_finished": "{} envoyé", "gui_download_in_progress": "Téléchargement démarré {}", - "gui_open_folder_error_nautilus": "Impossible d'ouvrir le dossier car nautilus n'est pas disponible. Le fichier est ici : {}", + "gui_open_folder_error_nautilus": "Impossible d’ouvrir le dossier, car nautilus n’est pas disponible. Le fichier est ici : {}", "gui_settings_language_label": "Langue préférée", "help_stealth": "Utilisation de l’autorisation client (avancé)", "help_receive": "Recevoir des partages au lieu de les envoyer", @@ -153,23 +153,23 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (estimation)", "gui_download_upload_progress_eta": "{0:s}, Fin : {1:s}, %p%", "error_rate_limit": "Quelqu’un a effectué trop de tentatives échouées sur votre adresse, ce qui signifie que cette personne pourrait essayer de la deviner. C’est pourquoi OnionShare a arrêté le serveur. Redémarrez le partage et envoyez au destinataire une nouvelle adresse pour partager.", - "error_stealth_not_supported": "Pour utiliser l’autorisation client, vous avez besoin d'au moins Tor 0.2.9.1-alpha (ou le navigateur Tor 6.5) et python3-stem 1.5.0.", - "gui_settings_stealth_option": "Utiliser l'autorisation client", + "error_stealth_not_supported": "Pour utiliser l’autorisation client, Tor 0.2.9.1-alpha (ou le Navigateur Tor 6.5) et python3-stem 1.5.0 ou versions ultérieures sont exigés.", + "gui_settings_stealth_option": "Utiliser l’autorisation client", "timeout_upload_still_running": "En attente de la fin de l'envoi", - "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée, vous pouvez maintenant\ncliquer pour copier votre HidServAuth.", - "gui_settings_autoupdate_check_button": "Vérifier les mises à jour", - "settings_test_success": "Connecté au contrôleur Tor.\n\nVersion de Tor : {}\nSupport des services oignon éphémères : {}.\nSupport de l'authentification client : {}.\nSupport de la nouvelle génération d'adresses .onion : {}.", - "update_error_check_error": "Impossible de vérifier les mises à jour : le site web d'OnionShare dit que la dernière version est la méconnaissable '{}'…", - "update_error_invalid_latest_version": "Impossible de vérifier les mises à jour : peut-être qu'il n'y a pas de connexion à Tor ou que le site web d'OnionShare est hors service ?", - "gui_tor_connection_ask": "Ouvrir les paramètres pour configurer la connexion à Tor ?", - "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nVérifiez votre connexion à Internet, puis réouvrez OnionShare et configurez sa connexion à Tor.", - "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses legacy", + "gui_settings_stealth_hidservauth_string": "Vous avez enregistré votre clé privée pour qu’elle puisse être réutilisée,\nvous pouvez maintenant cliquer pour copier votre HidServAuth.", + "gui_settings_autoupdate_check_button": "Vérifier s’il existe une nouvelle version", + "settings_test_success": "Vous êtes connecté au contrôleur Tor.\n\nVersion de Tor : {}\nPrend en charge les services onion éphémères : {}.\nPrend en charge l’authentification client : {}.\nPrend en charge la nouvelle génération d’adresses .onion : {}.", + "update_error_check_error": "Impossible de vérifier l’existence d’une mise à jour : le site Web d’OnionShare indique que la dernière version ne peut pas être reconnue '{}'…", + "update_error_invalid_latest_version": "Impossible de vérifier l’existence d’une mise à jour : êtes-vous bien connecté à Tor, le site Web d’OnionShare est-il hors service ?", + "gui_tor_connection_ask": "Ouvrir les paramètres pour résoudre le problème de connexion à Tor ?", + "gui_tor_connection_canceled": "Impossible de se connecter à Tor.\n\nAssurez-vous d’être connecté à Internet, puis rouvrez OnionShare et configurez sa connexion à Tor.", + "gui_use_legacy_v2_onions_checkbox": "Utiliser les adresses héritées", "info_in_progress_uploads_tooltip": "{} envoi(s) en cours", "info_completed_uploads_tooltip": "{} envoi(s) terminé(s)", "error_cannot_create_downloads_dir": "Impossible de créer le dossier du mode réception : {}", - "receive_mode_upload_starting": "Un téléversement d’une taille totale de {} commence", + "receive_mode_upload_starting": "Un téléversement d’une taille totale de {} commence", "systray_close_server_message": "Une personne a arrêté le serveur", - "systray_page_loaded_title": "Page chargée", + "systray_page_loaded_title": "La page a été chargée", "systray_download_page_loaded_message": "Une personne a chargé la page de téléchargement", "systray_upload_page_loaded_message": "Une personne a chargé la page d'envoi", "gui_share_stop_server_shutdown_timeout_tooltip": "La minuterie d’arrêt automatique se termine à {}", @@ -178,38 +178,40 @@ "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Utiliser les transports enfichables obfs4 intégrés (exige obfs4proxy)", "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Utiliser les transports enfichables meek_lite (Azure) intégrés", "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Utiliser les transports enfichables meek_lite (Azure) intégrés (exige obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

    Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor, par les transports obfs4 ou autres ponts normaux.", + "gui_settings_meek_lite_expensive_warning": "Avertissement : l’exploitation de ponts meek_lite demande beaucoup de ressources au Projet Tor.

    Ne les utilisez que si vous ne pouvez pas vous connecter directement à Tor par les transports obfs4 ou autres ponts normaux.", "gui_settings_shutdown_timeout_checkbox": "Utiliser la minuterie d’arrêt automatique", "gui_server_started_after_timeout": "La minuterie d’arrêt automatique est arrivée au bout de son délai avant le démarrage du serveur.\nVeuillez mettre en place un nouveau partage.", "gui_server_timeout_expired": "La minuterie d’arrêt automatique est déjà arrivée au bout de son délai.\nVeuillez la mettre à jour pour commencer le partage.", "close_on_timeout": "Arrêté, car la minuterie d’arrêt automatique est arrivée au bout de son délai", "gui_add_files": "Ajouter des fichiers", "gui_add_folder": "Ajouter un dossier", - "error_cannot_create_data_dir": "Impossible de créer le dossier de données OnionShare : {}", - "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", + "error_cannot_create_data_dir": "Impossible de créer le dossier de données d’OnionShare : {}", + "receive_mode_data_dir": "Les fichiers qui vous sont envoyés apparaissent dans ce dossier : {}", "gui_settings_data_dir_label": "Enregistrer les fichiers dans", "gui_settings_data_dir_browse_button": "Parcourir", - "systray_page_loaded_message": "Adresse OnionShare chargée", - "systray_share_started_title": "Le partage a commencé", - "systray_share_started_message": "Démarrer l'envoi de fichiers à une personne", + "systray_page_loaded_message": "L’adresse OnionShare a été chargée", + "systray_share_started_title": "Le partage est commencé", + "systray_share_started_message": "L’envoi de fichiers à quelqu’un est commencé", "systray_share_completed_title": "Le partage est terminé", - "systray_share_canceled_title": "Le partage a été annulé", - "systray_share_canceled_message": "Une personne a annulé la réception de vos fichiers", - "systray_receive_started_title": "Réception commencée", - "systray_receive_started_message": "Une personne vous envoie des fichiers", + "systray_share_canceled_title": "Le partage a été annulé", + "systray_share_canceled_message": "Quelqu’un a annulé la réception de vos fichiers", + "systray_receive_started_title": "La réception est commencée", + "systray_receive_started_message": "Quelqu’un vous envoie des fichiers", "gui_all_modes_history": "Historique", "gui_all_modes_clear_history": "Tout effacer", - "gui_all_modes_transfer_started": "{} démarré", - "gui_all_modes_transfer_finished_range": "Transféré {} - {}", - "gui_all_modes_transfer_finished": "Transféré {}", - "gui_all_modes_progress_complete": "%p%, {0:s} écoulé.", - "gui_all_modes_progress_starting": "{0:s}, %p% (estimation)", - "gui_all_modes_progress_eta": "{0:s}, Fin : {1:s}, %p%", - "gui_share_mode_no_files": "Pas encore de fichiers envoyés", - "gui_share_mode_timeout_waiting": "En attente de la fin de l'envoi", - "gui_receive_mode_no_files": "Pas encore de fichiers reçus", + "gui_all_modes_transfer_started": "Démarré le {}", + "gui_all_modes_transfer_finished_range": "Transféré le {} à {}", + "gui_all_modes_transfer_finished": "Transféré le {}", + "gui_all_modes_progress_complete": "%p %, {0:s} écoulé.", + "gui_all_modes_progress_starting": "{0:s}, %p % (estimation)", + "gui_all_modes_progress_eta": "{0:s}, fin prévue : {1:s}, %p %", + "gui_share_mode_no_files": "Aucun fichier n’a encore été envoyé", + "gui_share_mode_timeout_waiting": "En attente de la fin de l’envoi", + "gui_receive_mode_no_files": "Aucun fichier n’a encore été reçu", "gui_receive_mode_timeout_waiting": "En attente de la fin de la réception", - "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service Onion", - "systray_share_completed_message": "Terminé l'envoi de fichiers", - "gui_all_modes_transfer_canceled": "Annulé {}" + "gui_connect_to_tor_for_onion_settings": "Connectez-vous à Tor pour voir les paramètres du service onion", + "systray_share_completed_message": "L’envoi de fichiers est terminé", + "gui_all_modes_transfer_canceled": "Annulé le {}", + "gui_settings_onion_label": "Paramètres onion", + "gui_all_modes_transfer_canceled_range": "Annulé {} - {}" } diff --git a/share/locale/it.json b/share/locale/it.json index a21d6c5d..6e23cb1e 100644 --- a/share/locale/it.json +++ b/share/locale/it.json @@ -4,10 +4,10 @@ "ctrlc_to_stop": "Premi Ctrl+C per fermare il server", "not_a_file": "{0:s} non è un file valido.", "other_page_loaded": "URL caricato", - "closing_automatically": "Chiusura automatica, download completato", + "closing_automatically": "Fermato perchè il trasferimento è stato completato", "large_filesize": "Attenzione: inviare file di grandi dimensioni può richiedere ore", "help_local_only": "Non usare Tor (solo per lo sviluppo)", - "help_stay_open": "Mantieni il servizio avviato anche dopo aver completato il primo download", + "help_stay_open": "Mantieni la condivisione attiva anche dopo che i file sono stati inviati", "help_debug": "Registra gli errori sul disco", "help_filename": "Lista dei file o cartelle da condividere", "gui_drag_and_drop": "Trascina e rilascia i file e le cartelle per iniziare la condivisione", @@ -69,7 +69,7 @@ "gui_settings_whats_this": "Cos'è questo?", "help_receive": "Ricevi le condivisioni invece di inviarle", "gui_settings_stealth_option": "Usa l'autorizzazione client (legacy)", - "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi\ncliccare per copiare il tuo HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Avendo salvato la tua chiave privata per il riutilizzo, puoi cliccare per copiare il tuo HidServAuth.", "gui_settings_autoupdate_label": "Controlla se vi sono nuove versioni", "gui_settings_autoupdate_option": "Notificami quando è disponibile una nuova versione", "gui_settings_autoupdate_timestamp": "Ultimo controllo: {}", @@ -77,7 +77,7 @@ "gui_settings_autoupdate_check_button": "Controlla per una nuova versione", "gui_settings_general_label": "Impostazioni generali", "gui_settings_sharing_label": "Sto condividendo le impostazioni", - "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo il primo download", + "gui_settings_close_after_first_download_option": "Interrompe la condivisione dopo che i file sono stati inviati", "gui_settings_connection_type_label": "Come si dovrebbe connettere OnionShare a Tor?", "gui_settings_connection_type_bundled_option": "Usa la versione Tor integrata in OnionShare", "gui_settings_connection_type_automatic_option": "Tentativo di auto-configurazione con Tor Browser", @@ -174,7 +174,7 @@ "gui_settings_public_mode_checkbox": "Modalità pubblica", "systray_close_server_title": "Il server OnionShare è inattivo", "systray_close_server_message": "Un utente ha disattivato il Server", - "systray_page_loaded_title": "La pagina di OnionShare è stata caricata", + "systray_page_loaded_title": "Pagina caricata", "systray_download_page_loaded_message": "Un utente ha caricato la pagina di Download", "systray_upload_page_loaded_message": "Un utente ha caricato la pagina di Upload", "gui_uploads": "Storia degli Upload", @@ -184,5 +184,33 @@ "gui_upload_finished_range": "Upload eseguito {} a {}", "gui_upload_finished": "Caricato {}", "gui_download_in_progress": "Download iniziato {}", - "gui_open_folder_error_nautilus": "Impossibile aprire la cartella perché Nautilus non è disponibile. Il file è qui: {}" + "gui_open_folder_error_nautilus": "Impossibile aprire la cartella perché Nautilus non è disponibile. Il file è qui: {}", + "gui_settings_onion_label": "Impostazioni Onion", + "error_cannot_create_data_dir": "Non è possibile creare la cartella dati OnionShare: {}", + "receive_mode_data_dir": "I file inviati a te appariranno in questa cartella: {}", + "gui_settings_data_dir_label": "Salva i file in", + "gui_settings_data_dir_browse_button": "Naviga", + "systray_page_loaded_message": "Indirizzo OnionShare caricato", + "systray_share_started_title": "Condivisione iniziata", + "systray_share_started_message": "Inizio dell'invio dei file a qualcuno", + "systray_share_completed_title": "Condivisione completata", + "systray_share_completed_message": "Completato l'invio dei file", + "systray_share_canceled_title": "Condivisione annullata", + "systray_share_canceled_message": "Qualcuno ha annullato la ricezione dei file", + "systray_receive_started_title": "Inizio ricezione", + "systray_receive_started_message": "Qualcuno ti sta inviando dei file", + "gui_all_modes_history": "Storico", + "gui_all_modes_clear_history": "Pulisci tutto", + "gui_all_modes_transfer_started": "Iniziato {}", + "gui_all_modes_transfer_finished_range": "Trasferito {} - {}", + "gui_all_modes_transfer_finished": "Trasferito {}", + "gui_all_modes_transfer_canceled_range": "Annullato {} - {}", + "gui_all_modes_transfer_canceled": "Annullato {}", + "gui_all_modes_progress_complete": "%p%, {0:s} trascorsi.", + "gui_all_modes_progress_starting": "{0:s}, %p% (in calcolo)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Nessun file ancora inviato", + "gui_share_mode_timeout_waiting": "In attesa di finire l'invio", + "gui_receive_mode_no_files": "Nessun file ricevuto ancora", + "gui_receive_mode_timeout_waiting": "In attesa di finire la ricezione" } diff --git a/share/locale/ja.json b/share/locale/ja.json index fae2109e..ad559ca2 100644 --- a/share/locale/ja.json +++ b/share/locale/ja.json @@ -28,7 +28,7 @@ "help_stay_open": "ファイルが送信された後に共有し続けます", "help_shutdown_timeout": "数秒後に共有が停止されます", "help_stealth": "クライアント認証を使う(上級者向け)", - "help_receive": "送信の代わりに受信を優先する", + "help_receive": "共有を送信する代わりに受信する", "help_debug": "OnionShareのエラーを標準出力に、Webのエラーをディスクに記録する", "help_filename": "共有するファイルとフォルダの一覧", "help_config": "カスタムJSON設定ファイルの位置(任意)", @@ -60,7 +60,7 @@ "gui_download_upload_progress_starting": "{0:s}, %p% (計算中)", "gui_download_upload_progress_eta": "{0:s} 終了予定:{1:s}、%p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", - "gui_quit_title": "ちょっと待って", + "gui_quit_title": "そんなに速くない", "gui_share_quit_warning": "ファイルを送信中です。本当にOnionShareを終了しますか?", "gui_receive_quit_warning": "ファイルを受信中です。本当にOnionShareを終了しますか?", "gui_quit_warning_quit": "終了", @@ -72,7 +72,7 @@ "gui_settings_window_title": "設定", "gui_settings_whats_this": "これは何ですか?", "gui_settings_stealth_option": "クライアント認証を使用", - "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、\nクリックしてHidServAuthをコピーできます。", + "gui_settings_stealth_hidservauth_string": "秘密鍵を保存したので、クリックしてHidServAuthをコピーできます。", "gui_settings_autoupdate_label": "更新バージョンの有無をチェックする", "gui_settings_autoupdate_option": "更新通知を起動します", "gui_settings_autoupdate_timestamp": "前回にチェックした時: {}", @@ -211,5 +211,6 @@ "gui_share_mode_no_files": "送信されたファイルがまだありません", "gui_share_mode_timeout_waiting": "送信完了を待機しています", "gui_receive_mode_no_files": "受信されたファイルがまだありません", - "gui_receive_mode_timeout_waiting": "受信完了を待機しています" + "gui_receive_mode_timeout_waiting": "受信完了を待機しています", + "gui_settings_onion_label": "Onion設定" } diff --git a/share/locale/ko.json b/share/locale/ko.json index d4ce9d38..c5da4e9b 100644 --- a/share/locale/ko.json +++ b/share/locale/ko.json @@ -29,7 +29,7 @@ "help_shutdown_timeout": "정해진 초단위의 시간이 지난후 공유하는 것을 멈추시오", "help_stealth": "고객 허가를 사용 (고급 수준의)", "help_receive": "그것들을 보내는것 대신 공유를 받으시오", - "help_debug": "", + "help_debug": "어니언쉐어 에러들은 표준 출력 장치로 접속하고, 웹 에러들은 디스크로 접속 ", "help_filename": "", "help_config": "", "gui_drag_and_drop": "", diff --git a/share/locale/pt_BR.json b/share/locale/pt_BR.json index 0db55231..7ff64809 100644 --- a/share/locale/pt_BR.json +++ b/share/locale/pt_BR.json @@ -69,7 +69,7 @@ "gui_settings_window_title": "Configurações", "gui_settings_whats_this": "O que é isso?", "gui_settings_stealth_option": "Usar autorização de cliente", - "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode\nclicar para copiar o seu HidServAuth.", + "gui_settings_stealth_hidservauth_string": "Após salvar a sua chave privada para reutilização, você pode clicar para copiar o seu HidServAuth.", "gui_settings_autoupdate_label": "Procurar a nova versão", "gui_settings_autoupdate_option": "Notificar-me quando uma nova versão estiver disponível", "gui_settings_autoupdate_timestamp": "Última atualização: {}", diff --git a/share/locale/ru.json b/share/locale/ru.json index 539b488a..de372fe6 100644 --- a/share/locale/ru.json +++ b/share/locale/ru.json @@ -4,31 +4,31 @@ "not_a_file": "{0:s} недопустимый файл.", "gui_copied_url": "Ссылка OnionShare скопирована в буфер обмена", "other_page_loaded": "Адрес загружен", - "gui_copy_url": "Копировать ссылку", + "gui_copy_url": "Копировать адрес", "systray_menu_exit": "Выйти", "gui_add": "Добавить", "gui_delete": "Удалить", "gui_choose_items": "Выбрать", "gui_canceled": "Отменена", - "gui_quit_warning_quit": "Выйти", + "gui_quit_warning_quit": "Выход", "gui_quit_warning_dont_quit": "Отмена", "gui_settings_window_title": "Настройки", "gui_settings_autoupdate_timestamp_never": "Никогда", - "gui_settings_general_label": "Общие настройки:", + "gui_settings_general_label": "Общие настройки", "gui_settings_control_port_label": "Контрольный порт", "gui_settings_authenticate_password_option": "Пароль", "gui_settings_password_label": "Пароль", "gui_settings_button_save": "Сохранить", "gui_settings_button_cancel": "Отмена", "gui_settings_button_help": "Помощь", - "gui_tor_connection_ask_open_settings": "Настройки", + "gui_tor_connection_ask_open_settings": "Да", "gui_tor_connection_ask_quit": "Выйти", "gui_status_indicator_share_started": "Идёт отправка", "gui_status_indicator_receive_started": "Идёт получение", "gui_settings_downloads_label": "Путь сохранения файлов: ", "gui_settings_downloads_button": "Выбрать", "gui_clear_history": "Очистить Все", - "gui_settings_language_label": "Язык интерфейса:", + "gui_settings_language_label": "Язык интерфейса", "config_onion_service": "Назначем \"луковому\" сервису порт {:d}.", "preparing_files": "Сжимаем файлы.", "give_this_url_stealth": "Передайте этот адрес и строку HidServAuth получателю:", @@ -53,18 +53,18 @@ "help_stay_open": "Продолжить отправку после первого скачивания", "help_shutdown_timeout": "Остановить отправку после заданного количества секунд", "help_stealth": "Использовать авторизацию клиента (дополнительно)", - "help_receive": "Получать загрузки, вместо их отправки:", + "help_receive": "Получать загрузки вместо их отправки", "help_debug": "Направлять сообщения об ошибках OnionShare в stdout, ошибки сети сохранять на диск", "help_filename": "Список файлов или папок для отправки", "help_config": "Расположение пользовательского конфигурационного JSON-файла (необязательно)", "gui_drag_and_drop": "Перетащите сюда файлы и/или папки,\nкоторые хотите отправить.", "gui_share_start_server": "Начать отправку", "gui_share_stop_server": "Закончить отправку", - "gui_share_stop_server_shutdown_timeout": "Остановить отправку ({}s осталось)", + "gui_share_stop_server_shutdown_timeout": "Остановить отправку (осталось {}с)", "gui_share_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", "gui_receive_start_server": "Включить режим получения", "gui_receive_stop_server": "Выключить режим получения", - "gui_receive_stop_server_shutdown_timeout": "Выключить Режим Получения ({}s осталось)", + "gui_receive_stop_server_shutdown_timeout": "Выключить режим получения (осталось {}с)", "gui_receive_stop_server_shutdown_timeout_tooltip": "Время таймера истекает в {}", "gui_copy_hidservauth": "Скопировать строку HidServAuth", "gui_downloads": "История скачиваний", @@ -72,87 +72,87 @@ "gui_copied_url_title": "Адрес OnionShare скопирован", "gui_copied_hidservauth_title": "Строка HidServAuth скопирована", "gui_copied_hidservauth": "Строка HidServAuth скопирована в буфер обмена", - "gui_please_wait": "Запускается... Нажмите здесь, чтобы отменить.", + "gui_please_wait": "Запуск... Для отмены нажмите здесь.", "gui_download_upload_progress_complete": "%p%, прошло {0:s}.", "gui_download_upload_progress_starting": "{0:s}, %p% (вычисляем)", "gui_download_upload_progress_eta": "{0:s}, ETA: {1:s}, %p%", "version_string": "OnionShare {0:s} | https://onionshare.org/", "gui_quit_title": "Не так быстро", - "gui_share_quit_warning": "Идёт процесс отправки файлов. Вы уверены, что хотите завершить работу OnionShare?", - "gui_receive_quit_warning": "Идёт процесс получения файлов. Вы уверены, что хотите завершить работу OnionShare?", + "gui_share_quit_warning": "Идёт процесс отправки файлов. Уверены, что хотите завершить работу OnionShare?", + "gui_receive_quit_warning": "Идёт процесс получения файлов. Уверены, что хотите завершить работу OnionShare?", "error_rate_limit": "Кто-то совершил слишком много попыток подключения к Вашему серверу отправки файлов. Возможно, его пытаются вычислить. OnionShare остановил сервер. Отправьте Ваши данные повторно и перешлите получателю новый адрес.", "zip_progress_bar_format": "Сжатие: %p%", "error_stealth_not_supported": "Для использования авторизации клиента необходимы как минимум версии Tor 0.2.9.1-alpha (или Tor Browser 6.5) и библиотеки python3-stem 1.5.0.", "error_ephemeral_not_supported": "Для работы OnionShare необходимы как минимум версии Tor 0.2.7.1 и библиотеки python3-stem 1.4.0.", "gui_settings_whats_this": "Что это?", - "gui_settings_stealth_option": "Использовать авторизацию клиента (legacy)", - "gui_settings_stealth_hidservauth_string": "Сохранили Ваш приватный ключ для повторного использования,\nНажмите сюда, чтобы скопировать строку HidServAuth.", + "gui_settings_stealth_option": "Использовать авторизацию клиента", + "gui_settings_stealth_hidservauth_string": "Сохранили Ваш приватный ключ для повторного использования.\nНажмите сюда, чтобы скопировать строку HidServAuth.", "gui_settings_autoupdate_label": "Проверить наличие новой версии", "gui_settings_autoupdate_option": "Уведомить меня, когда будет доступна новая версия", "gui_settings_autoupdate_timestamp": "Последняя проверка: {}", "gui_settings_autoupdate_check_button": "Проверить наличие новой версии", - "gui_settings_sharing_label": "Настройки отправки:", + "gui_settings_sharing_label": "Настройки отправки", "gui_settings_close_after_first_download_option": "Завершить отправку Ваших файлов\nпосле их первого скачивания", "gui_settings_connection_type_label": "Как OnionShare следует подключаться к сети Tor?", "gui_settings_connection_type_bundled_option": "Использовать версию Tor, встроенную в OnionShare", "gui_settings_connection_type_automatic_option": "Автоматическая настройка при помощи Tor Browser", - "gui_settings_connection_type_control_port_option": "Использовать порт управления", + "gui_settings_connection_type_control_port_option": "Использовать контрольный порт", "gui_settings_connection_type_socket_file_option": "Использовать файл сокет", "gui_settings_connection_type_test_button": "Проверить подключение к сети Tor", "gui_settings_socket_file_label": "Файл сокет", "gui_settings_socks_label": "Порт SOCKS", "gui_settings_authenticate_label": "Настройки аутентификации Tor", "gui_settings_authenticate_no_auth_option": "Без аутентификации или cookie-аутентификации", - "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor:", + "gui_settings_tor_bridges": "Поддержка \"мостов\" Tor", "gui_settings_tor_bridges_no_bridges_radio_option": "Не использовать \"мосты\"", "gui_settings_tor_bridges_obfs4_radio_option": "Использовать встроенные подключаемые транспорты obfs4", "gui_settings_tor_bridges_obfs4_radio_option_no_obfs4proxy": "Использовать встроенные подключаемые транспорты obfs4 (необходим obfs4proxy)", - "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные meek_lite (Azure) встроенные транспорты", - "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные meek_lite (Azure) встроенные транспорты (необходим obfs4proxy)", - "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project

    Используйте их только если Вы не можете поделючться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".", + "gui_settings_tor_bridges_meek_lite_azure_radio_option": "Использовать встроенные транспорты meek_lite (Azure)", + "gui_settings_tor_bridges_meek_lite_azure_radio_option_no_obfs4proxy": "Использовать встроенные транспорты meek_lite (Azure) (необходим obfs4proxy)", + "gui_settings_meek_lite_expensive_warning": "Внимание: использование \"мостов\" meek_lite очень затратно для Tor Project.

    Используйте их только если не можете подключиться к сети Tor напрямую, через obfs4 транспорты или другие обычные \"мосты\".", "gui_settings_tor_bridges_custom_radio_option": "Использовать пользовательские \"мосты\"", - "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь https://bridges.torproject.org", - "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работет.\nПроверьте их снова или добавьте другие.", + "gui_settings_tor_bridges_custom_label": "Получить настройки \"мостов\" можно здесь: https://bridges.torproject.org", + "gui_settings_tor_bridges_invalid": "Ни один из добавленных вами \"мостов\" не работает.\nПроверьте их снова или добавьте другие.", "gui_settings_shutdown_timeout_checkbox": "Использовать таймер", "gui_settings_shutdown_timeout": "Остановить загрузку в:", - "settings_error_unknown": "Невозможно произвести подключение к контроллеру Tor, поскольку Ваши настройки не корректны.", - "settings_error_automatic": "Невозможно произвести подключение к контроллеру Tor. Пожалуйтса, уточните: Tor Browser (можно найти по ссылке: torproject.org) запущен в фоновом режиме?", - "settings_error_socket_port": "Невозможно произвести подключение к контроллеру Tor в {}:{}.", - "settings_error_socket_file": "Невозможно произвести подключение к контроллеру Tor используя файл-сокет {}.", - "settings_error_auth": "Произведено подлючение к {}:{}, не получается проверить подлинность. Возможно, это не контроллер сети Tor?", - "settings_error_missing_password": "Произведено подключение к контроллеру Tor, но для аутентификации необходим пароль.", - "settings_error_unreadable_cookie_file": "Произведено подключение к контроллеру Tor, но пароль может быть указан неверно или пользователю запрещено чтение файла-cookie.", - "settings_error_bundled_tor_not_supported": "Версию Tor, которая поставляется вместе с OnionShare нельзя использовать в режиме разработки на операционных системах Windows или macOS.", - "settings_error_bundled_tor_timeout": "Подключение к сети Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет или у вас неточно настроено системное время?", - "settings_error_bundled_tor_broken": "OnionShare не смог подулючиться к сети Tor в фоновом режиме:\n{}", - "settings_test_success": "Произведено подключение к контроллеру Tor.\n\nВерсия Tor: {}\nПоддержка временных \"луковых\" сервисов: {}.\nПоддержка аутентификации клиента: {}.\nПоддержка адресов .onion следующего поколения: {}.", - "error_tor_protocol_error": "Произошла ошибка с сетью Tor: {}", - "error_tor_protocol_error_unknown": "Произошла неизвестная ошибка с сетю Tor", - "error_invalid_private_key": "Данный тип приватного ключа не поддерживается", - "connecting_to_tor": "Идёт подключение к сети Tor...", - "update_available": "Вышла новая версия OnionShare. Нажмите сюда чтобы загрузить.

    Вы используется версию {}, наиболее поздняя версия {}.", - "update_error_check_error": "Не удалось проверить новые версии: сайт OnionShare сообщает, что не удалось распознать наиболее позднюю версию '{}'…", - "update_error_invalid_latest_version": "Не удалось проверить наличие новой версии: возможно, Вы не подключены к сети Tor или сайт OnionShare не работает?", - "update_not_available": "Вы используете наиболее позднюю версию OnionShare.", - "gui_tor_connection_ask": "Перейти в раздел \"Настройки\" для решения проблем с подключением к сети Tor?", - "gui_tor_connection_error_settings": "Попробуйте изменить способ, при помощий которого OnionShare подключается к сети Tor в разделе \"Настройки\".", - "gui_tor_connection_canceled": "Не удалось подключиться к сети Tor.\n\nПожалуйста, убедитесь что есть подключение к сети Интернет, затем переоткройте OnionShare и настройте подключение к сети Tor.", - "gui_tor_connection_lost": "Произведено отключение от сети Tor.", + "settings_error_unknown": "Невозможно произвести подключение к контроллеру Tor: некорректные настройки.", + "settings_error_automatic": "Ошибка подключения к контроллеру Tor. Запущен ли Tor Browser (torproject.org) в фоновом режиме?", + "settings_error_socket_port": "Ошибка подключения к контроллеру Tor в {}:{}.", + "settings_error_socket_file": "Ошибка подключения к контроллеру Tor с использованием файла-сокета {}.", + "settings_error_auth": "Подключено к {}:{}, ошибка проверки подлинности. Возможно, это не контроллер Tor?", + "settings_error_missing_password": "Подключено к контроллеру Tor, но для аутентификации нужен пароль.", + "settings_error_unreadable_cookie_file": "Подключено к контроллеру Tor, но возможна ошибка пароля, или пользователю запрещено чтение файла cookie.", + "settings_error_bundled_tor_not_supported": "Версия Tor, которая поставляется вместе с OnionShare, не подходит для разработчика в Windows и macOS.", + "settings_error_bundled_tor_timeout": "Подключение к Tor занимает слишком много времени. Возможно, отсутствует подключение к сети Интернет, или у вас неточно настроено системное время?", + "settings_error_bundled_tor_broken": "Ошибка подключения OnionShare к Tor в фоновом режиме:\n{}", + "settings_test_success": "Подключено к контроллеру Tor.\n\nВерсия Tor: {}\nПоддержка временных \"луковых\" сервисов: {}.\nПоддержка аутентификации клиента: {}.\nПоддержка адресов .onion следующего поколения: {}.", + "error_tor_protocol_error": "Ошибка Tor: {}", + "error_tor_protocol_error_unknown": "Неизвестная ошибка Tor", + "error_invalid_private_key": "Этот приватный ключ не поддерживается", + "connecting_to_tor": "Подключение к сети Tor", + "update_available": "Вышла новая версия OnionShare. Для загрузки нажмите сюда.

    Вы используется версию {}, наиболее свежая версия {}.", + "update_error_check_error": "Ошибка проверки новых версий: сайт OnionShare сообщает, что не удалось распознать наиболее свежую версию '{}'…", + "update_error_invalid_latest_version": "Ошибка проверки новой версии: возможно, вы не подключены к Tor, или сайт OnionShare не работает?", + "update_not_available": "Вы используете наиболее свежую версию OnionShare.", + "gui_tor_connection_ask": "Перейти в раздел \"Настройки\" для решения проблем с подключением к Tor?", + "gui_tor_connection_error_settings": "Попробуйте изменить способ подключения OnionShare к сети Tor в разделе \"Настройки\".", + "gui_tor_connection_canceled": "Ошибка подключения к Tor.\n\nПожалуйста, убедитесь что подключены к сети Интернет. Откройте OnionShare снова и настройте подключение к Tor.", + "gui_tor_connection_lost": "Отключено от Tor.", "gui_server_started_after_timeout": "Время таймера истекло до того, как сервер был запущен.\nПожалуйста, отправьте файлы заново.", - "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для того, чтобы начать отправку.", + "gui_server_timeout_expired": "Время таймера истекло.\nПожалуйста, обновите его для начала отправки.", "share_via_onionshare": "OnionShare это", "gui_use_legacy_v2_onions_checkbox": "Используйте устаревшие адреса", - "gui_save_private_key_checkbox": "Используйте постоянный адрес (legacy)", + "gui_save_private_key_checkbox": "Используйте постоянный адрес", "gui_share_url_description": "Кто угодно c этим адресом OnionShare может скачать Ваши файлы при помощи Tor Browser: ", - "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может загрузить файлы на Ваш комьютер Tor Browser: ", - "gui_url_label_persistent": "Данная отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", - "gui_url_label_stay_open": "Данная отправка не будет остановлена автоматически.", - "gui_url_label_onetime": "Данная отправка будет завершена автоматически после первой загрузки.", - "gui_url_label_onetime_and_persistent": "Данная отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_receive_url_description": "Кто угодно c этим адресом OnionShare может загрузить файлы на ваш компьютер с помощьюTor Browser: ", + "gui_url_label_persistent": "Эта отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать данный адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", + "gui_url_label_stay_open": "Эта отправка не будет остановлена автоматически.", + "gui_url_label_onetime": "Эта отправка будет завершена автоматически после первой загрузки.", + "gui_url_label_onetime_and_persistent": "Эта отправка не будет завершена автоматически.

    Каждая последующая отправка будет повторно использовать этот адрес. (Чтобы использовать одноразовый адрес, отключите опцию \"Использовать устаревший адрес\" в настройках.)", "gui_status_indicator_share_stopped": "Данные готовы к отправке", - "gui_status_indicator_share_working": "Ожидайте...", + "gui_status_indicator_share_working": "Ожидайте…", "gui_status_indicator_receive_stopped": "Данные готовы к получению", - "gui_status_indicator_receive_working": "Ожидайте...", + "gui_status_indicator_receive_working": "Ожидайте…", "gui_file_info": "{} файлы, {}", "gui_file_info_single": "{} файл, {}", "history_in_progress_tooltip": "{} в ходе выполнения", @@ -161,17 +161,17 @@ "info_completed_uploads_tooltip": "{} загрузка(и) завершена(ы)", "error_cannot_create_downloads_dir": "Не удалось создать папку в режиме получения: {}", "receive_mode_downloads_dir": "Загруженные Вас файлы находятся в папке: {}", - "receive_mode_warning": "Внимание: Режим получения позволяет другим людями загружать файлы на Ваш компьютер. Некоторые файлы могут представлять угрозу для Вашего компьютера.
    Открывайте файлы полученные только от тех людей, которым Вы доверяете или если Вы знаете, что делаете.", - "gui_receive_mode_warning": "Режим \"Получение Файлов\" позволяет другим людями загружать файлы на Ваш компьютер.

    Некоторые файлы могут представлять угрозу для Вашего компьютера.
    Открывайте файлы полученные только от тех людей, которым Вы доверяете или если Вы знаете, что делаете.
    ", + "receive_mode_warning": "Внимание: режим получения позволяет другим людям загружать файлы на ваш компьютер. Некоторые файлы могут представлять угрозу для вашего компьютера. Открывайте файлы от тех людей, которым вы доверяете, или если вы точно знаете, что делаете.", + "gui_receive_mode_warning": "Режим получения файлов позволяет другим людям загружать файлы на ваш компьютер.

    Некоторые файлы могут представлять угрозу для вашего компьютера. Открывайте файлы от тех людей, которым доверяете, или если вы точно знаете, что делаете.", "receive_mode_upload_starting": "Начинается загрузка общим объёмом {}", "receive_mode_received_file": "Получено: {}", "gui_mode_share_button": "Отправка файлов", "gui_mode_receive_button": "Получение файлов", - "gui_settings_receiving_label": "Настройки получения:", + "gui_settings_receiving_label": "Настройки получения", "gui_settings_public_mode_checkbox": "Публичный режим", "systray_close_server_title": "Сервер OnionShare отключен", "systray_close_server_message": "Пользователь отключил сервер", - "systray_page_loaded_title": "Страница OnionShare загружена", + "systray_page_loaded_title": "Страница загружена", "systray_download_page_loaded_message": "Пользователь находится на странице скачивания", "systray_upload_page_loaded_message": "Пользователь посетил странцу загрузки", "gui_uploads": "История загрузок", @@ -180,9 +180,37 @@ "gui_upload_finished_range": "Загружено {} в {}", "gui_upload_finished": "Загружено {}", "gui_download_in_progress": "Загрузка началась {}", - "gui_open_folder_error_nautilus": "Не удаётся открыть папку, поскольку файловый менджер Nautilus не доступен. Файл находится здесь: {}", + "gui_open_folder_error_nautilus": "Не удаётся открыть папку без файлового менеджера Nautilus. Файл находится здесь: {}", "gui_settings_language_changed_notice": "Перезапустите OnionShare, чтобы изменения языковых настроек вступили в силу.", "gui_add_files": "Добавить файлы", "gui_add_folder": "Добавить папку", - "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}" + "error_cannot_create_data_dir": "Не удалось создать папку данных OnionShare: {}", + "gui_settings_onion_label": "Настройки \"лукового\" сервиса", + "gui_connect_to_tor_for_onion_settings": "Подключитесь к Tor, чтобы видеть настройки \"лукового\" сервиса", + "receive_mode_data_dir": "Отправленные Вам файлы можно найти в этой папке: {}", + "gui_settings_data_dir_label": "Сохранять файлы в", + "gui_settings_data_dir_browse_button": "Выбрать", + "systray_page_loaded_message": "Адрес OnionShare загружен", + "systray_share_started_title": "Отправка Началась", + "systray_share_started_message": "Началась отправка файлов", + "systray_share_completed_title": "Отправка завершена", + "systray_share_completed_message": "Завершена отправка файлов", + "systray_share_canceled_title": "Отправка отменена", + "systray_share_canceled_message": "Кто-то отменил получение Ваших файлов", + "systray_receive_started_title": "Загрузка началась", + "systray_receive_started_message": "Кто-то отправляет Вам файлы", + "gui_all_modes_history": "История", + "gui_all_modes_clear_history": "Очистить историю полностью", + "gui_all_modes_transfer_started": "Начато {}", + "gui_all_modes_transfer_finished_range": "Отправлено {} - {}", + "gui_all_modes_transfer_finished": "Отправлено {}", + "gui_all_modes_transfer_canceled_range": "Отменено {} - {}", + "gui_all_modes_transfer_canceled": "Отменено {}", + "gui_all_modes_progress_complete": "%p%, {0:s.} прошло.", + "gui_all_modes_progress_starting": "{0:s}, %p% (вычисляем)", + "gui_all_modes_progress_eta": "{0:s}, ETA: {1:s}, %p%", + "gui_share_mode_no_files": "Пока нет отправленных файлов", + "gui_share_mode_timeout_waiting": "Ожидается завершение отправки", + "gui_receive_mode_no_files": "Пока нет полученных файлов", + "gui_receive_mode_timeout_waiting": "Ожидается завершение загрузки" } diff --git a/share/locale/zh_Hans.json b/share/locale/zh_Hans.json index 2331e762..6a3bf7b7 100644 --- a/share/locale/zh_Hans.json +++ b/share/locale/zh_Hans.json @@ -209,5 +209,8 @@ "gui_share_mode_no_files": "还没有文件发出", "gui_share_mode_timeout_waiting": "等待结束发送", "gui_receive_mode_no_files": "还没有接收文件", - "gui_receive_mode_timeout_waiting": "等待接收完成" + "gui_receive_mode_timeout_waiting": "等待接收完成", + "gui_settings_onion_label": "Onion设置", + "gui_all_modes_transfer_canceled_range": "已取消 {} - {}", + "gui_all_modes_transfer_canceled": "已取消 {}" } -- cgit v1.2.3-54-g00ecf From 221518f0c4f44b3ebecf110379d62c6554de6ce7 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 10:40:24 -0800 Subject: Update available languages --- onionshare/settings.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/onionshare/settings.py b/onionshare/settings.py index 1570a150..68cbb857 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -57,21 +57,19 @@ class Settings(object): # Dictionary of available languages in this version of OnionShare, # mapped to the language name, in that language self.available_locales = { - 'cs': 'Hrvatski', # Croatian + 'bn': 'বাংলা', # Bengali + 'ca': 'Català', # Catalan 'da': 'Dansk', # Danish - 'de': 'Deutsch', # German 'en': 'English', # English - 'eo': 'Esperanto', # Esperanto - 'es': 'Español', # Spanish - 'fi': 'Suomi', # Finnish 'fr': 'Français', # French + 'el': 'Ελληνικά', # Greek 'it': 'Italiano', # Italian - 'nl': 'Nederlands', # Dutch - 'no': 'Norsk', # Norwegian - 'pt_BR': 'Português Brasil', # Portuguese Brazil - 'pt_PT': 'Português Portugal', # Portuguese Portugal + 'ja': '日本語', # Japanese + 'fa': 'فارسی', # Persian + 'pt_BR': 'Português (Brasil)', # Portuguese Brazil 'ru': 'Русский', # Russian - 'tr': 'Türkçe' # Turkish + 'es': 'Español', # Spanish + 'sv': 'Svenska' # Swedish } # These are the default settings. They will get overwritten when loading from disk -- cgit v1.2.3-54-g00ecf From 462c32fb054f8d6f5f6f4de74d297838c7aac16d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 10:49:57 -0800 Subject: Update changelog, and version bump to 2.0 --- CHANGELOG.md | 6 +++--- install/onionshare.nsi | 2 +- share/version.txt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f0b7aba..a4a8ba4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,14 @@ # OnionShare Changelog -## 2.0.dev2 +## 2.0 * New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files +* New feature: Support for next generation onion services * New feature: macOS sandbox is enabled -* New feature: Support for next generation onion services (TODO waiting on Tor release) * New feature: If you're sharing a single file, don't zip it up * New feature: Full support for meek_lite (Azure) bridges * New feature: Allow selecting your language from a dropdown -* New translations: (TODO fill in for final release) +* New translations: Bengali (বাংলা), Catalan (Català), Danish (Dansk), French (Français), Greek (Ελληνικά), Italian (Italiano), Japanese (日本語), Persian (فارسی), Portuguese Brazil (Português Brasil), Russian (Русский), Spanish (Español), Swedish (Svenska) * Several bugfixes * Invisible to users, but this version includes some major refactoring of the codebase, and a robust set of unit tests which makes OnionShare easier to maintain going forward diff --git a/install/onionshare.nsi b/install/onionshare.nsi index d29e10a5..3a4c6c2a 100644 --- a/install/onionshare.nsi +++ b/install/onionshare.nsi @@ -6,7 +6,7 @@ !define INSTALLSIZE 115186 !define VERSIONMAJOR 2 !define VERSIONMINOR 0 -!define VERSIONSTRING "2.0.dev3" +!define VERSIONSTRING "2.0" RequestExecutionLevel admin diff --git a/share/version.txt b/share/version.txt index c0099a45..cd5ac039 100644 --- a/share/version.txt +++ b/share/version.txt @@ -1 +1 @@ -2.0.dev3 +2.0 -- cgit v1.2.3-54-g00ecf From ace1142056967b73178371ee9283beb0b1c3ea82 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 10:59:46 -0800 Subject: Update readme --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 68b93574..59333884 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [OnionShare](https://onionshare.org) lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service. -If you want to send files to someone, OnionShare hosts them on your own computer and uses a Tor onion service to make them temporarily accessible over the internet. The receiving user just needs to open the web address in Tor Browser to download the files. If you want to receive files, OnionShare hosts an anonymous dropbox directly on your computer and uses a Tor onion service to make it temporarily accessible over the internet. Other users can upload files to you from by loading the web address in Tor Browser. +OnionShare is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in Tor Browser to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account. Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing files with can access them. ## Documentation @@ -12,20 +12,18 @@ To learn how OnionShare works, what its security properties are, and how to use You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). -For Ubuntu-like distributions, you could use this PPA to get the latest version: +For Ubuntu-like Linux distributions, you could use this PPA to get the latest version: ``` sudo add-apt-repository ppa:micahflee/ppa sudo apt install -y onionshare ``` -OnionShare also may be available in your operating system's package manager: +OnionShare may also be available in your Linux distribution's package manager. Check [this wiki page](https://github.com/micahflee/onionshare/wiki/How-Do-I-Install-Onionshare) for more information. -[![Packaging status](https://repology.org/badge/vertical-allrepos/onionshare.svg)](https://repology.org/metapackage/onionshare/versions) +## Contributing to OnionShare -## Developing OnionShare - -You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our developers mailing list [here](https://lists.riseup.net/www/info/onionshare-dev). +You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our mailing list [here](https://lists.riseup.net/www/info/onionshare-dev), and join our public Keybase team [here](https://keybase.io/team/onionshare). Test status: [![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) -- cgit v1.2.3-54-g00ecf From 7ee8c3e4022e97cf997431ab438302ccf9a8969e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 11:00:58 -0800 Subject: Update readme again --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 59333884..30a3a1a5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # OnionShare -[OnionShare](https://onionshare.org) lets you securely and anonymously send and receive files. It works by starting a web server, making it accessible as a Tor onion service, and generating an unguessable web address so others can download files from you, or upload files to you. It does _not_ require setting up a separate server or using a third party file-sharing service. - -OnionShare is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in Tor Browser to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account. Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing files with can access them. +[OnionShare](https://onionshare.org) is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in Tor Browser to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account. Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing files with can access them. ## Documentation -- cgit v1.2.3-54-g00ecf From b0bdd166b0826e2695bfd0c3f13946b01c713a29 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 11:30:40 -0800 Subject: Update build instructions --- BUILD.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index b01faaf1..3d664ac8 100644 --- a/BUILD.md +++ b/BUILD.md @@ -56,7 +56,6 @@ Now install some python dependencies with pip (note, there's issues building a . ```sh pip3 install -r install/requirements.txt -pip3 install PyInstaller==3.4 ``` #### You can run both the CLI and GUI versions of OnionShare without building an bundle @@ -66,6 +65,48 @@ pip3 install PyInstaller==3.4 ./dev_scripts/onionshare-gui ``` +#### Building PyInstaller + +If you want to build an app bundle, you'll need to use PyInstaller. Recently there has been issues with installing PyInstaller using pip, so here's how to build it from source. First, make sure you don't have PyInstaller currently installed: + +```sh +pip3 uninstall PyInstaller +``` + +Change to a folder where you keep source code, and clone the PyInstaller git repo: + +```sh +git clone https://github.com/pyinstaller/pyinstaller.git +``` + +Verify the v3.4 git tag: + +```sh +cd pyinstaller +gpg --keyserver hkps://keyserver.ubuntu.com:443 --recv-key 0xD4AD8B9C167B757C4F08E8777B752811BF773B65 +git tag -v v3.4 +``` + +It should say `Good signature from "Hartmut Goebel `. If it verified successfully, checkout the tag: + +```sh +git checkout v3.4 +``` + +And compile the bootloader, following [these instructions](https://pyinstaller.readthedocs.io/en/stable/bootloader-building.html#building-for-mac-os-x). To compile, run this: + +```sh +cd bootloader +python3 waf distclean all --target-arch=64bit +``` + +Finally, install the PyInstaller module into your local site-packages: + +```sh +cd .. +python3 setup.py install +``` + #### To build the app bundle ```sh @@ -185,7 +226,8 @@ python waf distclean all --target-arch=32bit --msvc_targets=x86 Finally, install the PyInstaller module into your local site-packages: ``` -pythin setup.py install +cd .. +python setup.py install ``` Now the next time you use PyInstaller to build OnionShare, the `.exe` file should not be flagged as malicious by anti-virus. -- cgit v1.2.3-54-g00ecf From 9b6ce4df22e85fa0477ef773631ad7aee57f039c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 15:56:10 -0800 Subject: Tweak readme again --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30a3a1a5..6b6ed836 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # OnionShare -[OnionShare](https://onionshare.org) is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in Tor Browser to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account. Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing files with can access them. +[OnionShare](https://onionshare.org) is an open source tool for securely and anonymously sending and receiving files using Tor onion services. It works by starting a web server directly on your computer and making it accessible as an unguessable Tor web address that others can load in [Tor Browser](https://www.torproject.org/) to download files from you, or upload files to you. It doesn't require setting up a separate server, using a third party file-sharing service, or even logging into an account. + +Unlike services like email, Google Drive, DropBox, WeTransfer, or nearly any other way people typically send files to each other, when you use OnionShare you don't give any companies access to the files that you're sharing. So long as you share the unguessable web address in a secure way (like pasting it in an encrypted messaging app), _no one_ but you and the person you're sharing with can access the files. ## Documentation -- cgit v1.2.3-54-g00ecf From 181fed1a4aff15cfe9a67f60b56ac0d70f9c1fe6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 10 Feb 2019 16:14:42 -0800 Subject: Add public mode to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a8ba4e..392df41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * New feature: Receiver mode allows you to receive files with OnionShare, instead of only sending files * New feature: Support for next generation onion services * New feature: macOS sandbox is enabled +* New feature: Public mode feature, for public uses of OnionShare, which when enabled turns off slugs in the URL and removes the limit on how many 404 requests can be made * New feature: If you're sharing a single file, don't zip it up * New feature: Full support for meek_lite (Azure) bridges * New feature: Allow selecting your language from a dropdown -- cgit v1.2.3-54-g00ecf From 7c71aef15b1038c3eeac08fe5a8fa69b9d40c9f9 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Feb 2019 11:36:18 +1100 Subject: Support persistent v3 onions --- onionshare/onion.py | 15 ++------------- onionshare_gui/settings_dialog.py | 34 ++-------------------------------- tests/SettingsGuiBaseTest.py | 8 ++++---- 3 files changed, 8 insertions(+), 49 deletions(-) diff --git a/onionshare/onion.py b/onionshare/onion.py index 4d9cbacf..ed4fde7b 100644 --- a/onionshare/onion.py +++ b/onionshare/onion.py @@ -451,20 +451,9 @@ class Onion(object): key_content = self.settings.get('private_key') if self.is_v2_key(key_content): key_type = "RSA1024" - # The below section is commented out because re-publishing - # a pre-prepared v3 private key is currently unstable in Tor. - # This is fixed upstream but won't reach stable until 0.3.5 - # (expected in December 2018) - # See https://trac.torproject.org/projects/tor/ticket/25552 - # Until then, we will deliberately not work with 'persistent' - # v3 onions, which should not be possible via the GUI settings - # anyway. - # Our ticket: https://github.com/micahflee/onionshare/issues/677 - # - # Assume it was a v3 key - # key_type = "ED25519-V3" else: - raise TorErrorProtocolError(strings._('error_invalid_private_key')) + # Assume it was a v3 key. Stem will throw an error if it's something illegible + key_type = "ED25519-V3" else: key_type = "NEW" diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 75a2f59b..9bc5f254 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -122,7 +122,6 @@ class SettingsDialog(QtWidgets.QDialog): self.save_private_key_checkbox = QtWidgets.QCheckBox() self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) 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) 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) @@ -498,22 +497,17 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') if use_legacy_v2_onions: - self.save_private_key_widget.show() self.use_stealth_widget.show() else: - self.save_private_key_widget.hide() self.use_stealth_widget.hide() save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) - # Legacy v2 mode is forced on if persistence is enabled - self.use_legacy_v2_onions_checkbox.setEnabled(False) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) - self.use_legacy_v2_onions_checkbox.setEnabled(True) - if use_legacy_v2_onions or save_private_key: + if use_legacy_v2_onions: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) data_dir = self.old_settings.get('data_dir') @@ -535,8 +529,6 @@ class SettingsDialog(QtWidgets.QDialog): self.hidservauth_copy_button.show() else: self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) - if not save_private_key: - self.use_legacy_v2_onions_checkbox.setEnabled(True) use_autoupdate = self.old_settings.get('use_autoupdate') if use_autoupdate: @@ -727,23 +719,10 @@ class SettingsDialog(QtWidgets.QDialog): Show the legacy settings if the legacy mode is enabled. """ if checked: - self.save_private_key_widget.show() self.use_stealth_widget.show() else: - self.save_private_key_widget.hide() self.use_stealth_widget.hide() - def save_private_key_checkbox_clicked(self, checked): - """ - Prevent the v2 legacy mode being switched off if persistence is enabled - """ - if checked: - self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) - self.use_legacy_v2_onions_checkbox.setEnabled(False) - else: - if not self.stealth_checkbox.isChecked(): - self.use_legacy_v2_onions_checkbox.setEnabled(True) - def stealth_checkbox_clicked_connect(self, checked): """ Prevent the v2 legacy mode being switched off if stealth is enabled @@ -752,8 +731,7 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) self.use_legacy_v2_onions_checkbox.setEnabled(False) else: - if not self.save_private_key_checkbox.isChecked(): - self.use_legacy_v2_onions_checkbox.setEnabled(True) + self.use_legacy_v2_onions_checkbox.setEnabled(True) def data_dir_button_clicked(self): """ @@ -965,8 +943,6 @@ class SettingsDialog(QtWidgets.QDialog): use_legacy_v2_onions = False if self.save_private_key_checkbox.isChecked(): - # force the legacy mode on - use_legacy_v2_onions = True settings.set('save_private_key', True) settings.set('private_key', self.old_settings.get('private_key')) settings.set('slug', self.old_settings.get('slug')) @@ -982,12 +958,6 @@ class SettingsDialog(QtWidgets.QDialog): settings.set('use_legacy_v2_onions', True) else: settings.set('use_legacy_v2_onions', False) - # If we are not using legacy mode, but we previously had persistence turned on, force it off! - settings.set('save_private_key', False) - settings.set('private_key', '') - settings.set('slug', '') - # Also unset the HidServAuth if we are removing our reusable private key - settings.set('hidservauth_string', '') settings.set('data_dir', self.data_dir_lineedit.text()) settings.set('public_mode', self.public_mode_checkbox.isChecked()) diff --git a/tests/SettingsGuiBaseTest.py b/tests/SettingsGuiBaseTest.py index 71244e0f..844a0c86 100644 --- a/tests/SettingsGuiBaseTest.py +++ b/tests/SettingsGuiBaseTest.py @@ -88,8 +88,8 @@ class SettingsGuiBaseTest(object): if self.gui.onion.supports_v3_onions: # legacy mode is off self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isChecked()) - # persistence, stealth is hidden and disabled - self.assertFalse(self.gui.save_private_key_widget.isVisible()) + # persistence is still available, stealth is hidden and disabled + self.assertTrue(self.gui.save_private_key_widget.isVisible()) self.assertFalse(self.gui.save_private_key_checkbox.isChecked()) self.assertFalse(self.gui.use_stealth_widget.isVisible()) self.assertFalse(self.gui.stealth_checkbox.isChecked()) @@ -107,7 +107,7 @@ class SettingsGuiBaseTest(object): # enable stealth mode QtTest.QTest.mouseClick(self.gui.stealth_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.stealth_checkbox.height()/2)) self.assertTrue(self.gui.stealth_checkbox.isChecked()) - # now that stealth, persistence are enabled, we can't turn off legacy mode + # now that stealth is enabled, we can't turn off legacy mode self.assertFalse(self.gui.use_legacy_v2_onions_checkbox.isEnabled()) # disable stealth, persistence QtTest.QTest.mouseClick(self.gui.save_private_key_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.save_private_key_checkbox.height()/2)) @@ -117,7 +117,7 @@ class SettingsGuiBaseTest(object): # uncheck legacy mode QtTest.QTest.mouseClick(self.gui.use_legacy_v2_onions_checkbox, QtCore.Qt.LeftButton, pos=QtCore.QPoint(2,self.gui.use_legacy_v2_onions_checkbox.height()/2)) # legacy options hidden again - self.assertFalse(self.gui.save_private_key_widget.isVisible()) + self.assertTrue(self.gui.save_private_key_widget.isVisible()) self.assertFalse(self.gui.use_stealth_widget.isVisible()) # re-enable legacy mode -- cgit v1.2.3-54-g00ecf From 366509a75ca45345775ab7677d420d924642ce21 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 11 Feb 2019 16:23:47 +1100 Subject: Re-order so persistence checkbox comes before legacy mode checkbox. Remove superfluous conditional --- onionshare_gui/settings_dialog.py | 46 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py index 9bc5f254..2933784c 100644 --- a/onionshare_gui/settings_dialog.py +++ b/onionshare_gui/settings_dialog.py @@ -101,6 +101,22 @@ class SettingsDialog(QtWidgets.QDialog): self.connect_to_tor_label = QtWidgets.QLabel(strings._("gui_connect_to_tor_for_onion_settings")) self.connect_to_tor_label.setStyleSheet(self.common.css['settings_connect_to_tor']) + # 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.setCheckState(QtCore.Qt.Unchecked) + self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) + 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) + save_private_key_label.setOpenExternalLinks(True) + save_private_key_layout = QtWidgets.QHBoxLayout() + save_private_key_layout.addWidget(self.save_private_key_checkbox) + save_private_key_layout.addWidget(save_private_key_label) + save_private_key_layout.addStretch() + save_private_key_layout.setContentsMargins(0,0,0,0) + self.save_private_key_widget = QtWidgets.QWidget() + self.save_private_key_widget.setLayout(save_private_key_layout) + # Whether or not to use legacy v2 onions self.use_legacy_v2_onions_checkbox = QtWidgets.QCheckBox() self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Unchecked) @@ -118,22 +134,6 @@ class SettingsDialog(QtWidgets.QDialog): self.use_legacy_v2_onions_widget = QtWidgets.QWidget() self.use_legacy_v2_onions_widget.setLayout(use_legacy_v2_onions_layout) - # 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.setCheckState(QtCore.Qt.Unchecked) - self.save_private_key_checkbox.setText(strings._("gui_save_private_key_checkbox")) - 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.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - save_private_key_label.setOpenExternalLinks(True) - save_private_key_layout = QtWidgets.QHBoxLayout() - save_private_key_layout.addWidget(self.save_private_key_checkbox) - save_private_key_layout.addWidget(save_private_key_label) - save_private_key_layout.addStretch() - save_private_key_layout.setContentsMargins(0,0,0,0) - self.save_private_key_widget = QtWidgets.QWidget() - self.save_private_key_widget.setLayout(save_private_key_layout) - # Stealth self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) @@ -164,8 +164,8 @@ class SettingsDialog(QtWidgets.QDialog): # Onion settings widget onion_settings_layout = QtWidgets.QVBoxLayout() onion_settings_layout.setContentsMargins(0, 0, 0, 0) - onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget) onion_settings_layout.addWidget(self.save_private_key_widget) + onion_settings_layout.addWidget(self.use_legacy_v2_onions_widget) onion_settings_layout.addWidget(self.use_stealth_widget) onion_settings_layout.addWidget(self.hidservauth_details) onion_settings_layout.addWidget(self.hidservauth_copy_button) @@ -494,21 +494,19 @@ class SettingsDialog(QtWidgets.QDialog): else: self.shutdown_timeout_checkbox.setCheckState(QtCore.Qt.Unchecked) - use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') - - if use_legacy_v2_onions: - self.use_stealth_widget.show() - else: - self.use_stealth_widget.hide() - save_private_key = self.old_settings.get('save_private_key') if save_private_key: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Checked) else: self.save_private_key_checkbox.setCheckState(QtCore.Qt.Unchecked) + use_legacy_v2_onions = self.old_settings.get('use_legacy_v2_onions') + if use_legacy_v2_onions: self.use_legacy_v2_onions_checkbox.setCheckState(QtCore.Qt.Checked) + self.use_stealth_widget.show() + else: + self.use_stealth_widget.hide() data_dir = self.old_settings.get('data_dir') self.data_dir_lineedit.setText(data_dir) -- cgit v1.2.3-54-g00ecf From 9115ff89f3754e2cf952a8821c1f2469c9fca55b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Feb 2019 22:46:39 -0800 Subject: Refactor receive mode to start saving files to data_dir with .part extension while they're downloading --- onionshare/web/receive_mode.py | 172 ++++++++++++++++++++--------------------- onionshare/web/web.py | 2 +- 2 files changed, 83 insertions(+), 91 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index f035271a..be68900e 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -58,104 +58,37 @@ class ReceiveModeWeb(object): def upload_logic(slug_candidate=''): """ - Upload files. + Handle the upload files POST request, though at this point, the files have + already been uploaded and saved to their correct locations. """ - # Figure out what the receive mode dir should be - now = datetime.now() - date_dir = now.strftime("%Y-%m-%d") - time_dir = now.strftime("%H.%M.%S") - receive_mode_dir = os.path.join(self.common.settings.get('data_dir'), date_dir, time_dir) - valid = True - try: - os.makedirs(receive_mode_dir, 0o700, exist_ok=True) - except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": receive_mode_dir - }) - print(strings._('error_cannot_create_data_dir').format(receive_mode_dir)) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - files = request.files.getlist('file[]') filenames = [] - print('') for f in files: if f.filename != '': - # Automatically rename the file, if a file of the same name already exists filename = secure_filename(f.filename) filenames.append(filename) - local_path = os.path.join(receive_mode_dir, filename) - if os.path.exists(local_path): - if '.' in filename: - # Add "-i", e.g. change "foo.txt" to "foo-2.txt" - parts = filename.split('.') - name = parts[:-1] - ext = parts[-1] - - i = 2 - valid = False - while not valid: - new_filename = '{}-{}.{}'.format('.'.join(name), i, ext) - local_path = os.path.join(receive_mode_dir, new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - else: - # If no extension, just add "-i", e.g. change "foo" to "foo-2" - i = 2 - valid = False - while not valid: - new_filename = '{}-{}'.format(filename, i) - local_path = os.path.join(receive_mode_dir, new_filename) - if os.path.exists(local_path): - i += 1 - else: - valid = True - + local_path = os.path.join(request.receive_mode_dir, filename) basename = os.path.basename(local_path) - if f.filename != basename: - # Tell the GUI that the file has changed names - self.web.add_request(self.web.REQUEST_UPLOAD_FILE_RENAMED, request.path, { - 'id': request.upload_id, - 'old_filename': f.filename, - 'new_filename': basename - }) # Tell the GUI the receive mode directory for this file self.web.add_request(self.web.REQUEST_UPLOAD_SET_DIR, request.path, { 'id': request.upload_id, 'filename': basename, - 'dir': receive_mode_dir + 'dir': request.receive_mode_dir }) - # Make sure receive mode dir exists before writing file - valid = True - try: - os.makedirs(receive_mode_dir, 0o700, exist_ok=True) - except PermissionError: - self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { - "receive_mode_dir": receive_mode_dir - }) - print(strings._('error_cannot_create_data_dir').format(receive_mode_dir)) - valid = False - if not valid: - flash('Error uploading, please inform the OnionShare user', 'error') - if self.common.settings.get('public_mode'): - return redirect('/') - else: - return redirect('/{}'.format(slug_candidate)) - self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) print(strings._('receive_mode_received_file').format(local_path)) - f.save(local_path) - # Note that flash strings are on English, and not translated, on purpose, + if request.upload_error: + self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error') + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): + return redirect('/') + else: + return redirect('/{}'.format(slug_candidate)) + + # Note that flash strings are in English, and not translated, on purpose, # to avoid leaking the locale of the OnionShare user if len(filenames) == 0: flash('No files uploaded', 'info') @@ -198,7 +131,6 @@ class ReceiveModeWeb(object): return upload_logic() - class ReceiveModeWSGIMiddleware(object): """ Custom WSGI middleware in order to attach the Web object to environ, so @@ -214,10 +146,11 @@ class ReceiveModeWSGIMiddleware(object): return self.app(environ, start_response) -class ReceiveModeTemporaryFile(object): +class ReceiveModeFile(object): """ - A custom TemporaryFile that tells ReceiveModeRequest every time data gets - written to it, in order to track the progress of uploads. + A custom file object that tells ReceiveModeRequest every time data gets + written to it, in order to track the progress of uploads. It starts out with + a .part file extension, and when it's complete it removes that extension. """ def __init__(self, request, filename, write_func, close_func): self.onionshare_request = request @@ -225,8 +158,17 @@ class ReceiveModeTemporaryFile(object): self.onionshare_write_func = write_func self.onionshare_close_func = close_func - # Create a temporary file - self.f = tempfile.TemporaryFile('wb+') + self.filename = os.path.join(self.onionshare_request.receive_mode_dir, secure_filename(filename)) + self.filename_in_progress = '{}.part'.format(self.filename) + + # Open the file + try: + self.f = open(self.filename_in_progress, 'wb+') + except: + # This will only happen if someone is messaging with the data dir while + # OnionShare is running, but if it does make sure to throw an error + self.upload_error = True + self.f = tempfile.TemporaryFile('wb+') # Make all the file-like methods and attributes actually access the # TemporaryFile, except for write @@ -241,7 +183,7 @@ class ReceiveModeTemporaryFile(object): """ Custom write method that calls out to onionshare_write_func """ - if not self.onionshare_request.stop_q.empty(): + if self.upload_error or (not self.onionshare_request.stop_q.empty()): self.close() self.onionshare_request.close() return @@ -254,6 +196,11 @@ class ReceiveModeTemporaryFile(object): Custom close method that calls out to onionshare_close_func """ self.f.close() + + if not self.upload_error: + # Rename the in progress file to the final filename + os.rename(self.filename_in_progress, self.filename) + self.onionshare_close_func(self.onionshare_filename) @@ -283,6 +230,49 @@ class ReceiveModeRequest(Request): self.upload_request = True if self.upload_request: + # No errors yet + self.upload_error = False + + # Figure out what files should be saved + now = datetime.now() + date_dir = now.strftime("%Y-%m-%d") + time_dir = now.strftime("%H.%M.%S") + self.receive_mode_dir = os.path.join(self.web.common.settings.get('data_dir'), date_dir, time_dir) + + # Create that directory, which shouldn't exist yet + try: + os.makedirs(self.receive_mode_dir, 0o700, exist_ok=False) + except OSError: + # If this directory already exists, maybe someone else is uploading files at + # the same second, so use a different name in that case + if os.path.exists(self.receive_mode_dir): + # Keep going until we find a directory name that's available + i = 1 + while True: + new_receive_mode_dir = '{}-{}'.format(self.receive_mode_dir, i) + try: + os.makedirs(new_receive_mode_dir, 0o700, exist_ok=False) + break + except OSError: + pass + i += 1 + # Failsafe + if i == 100: + self.web.common.log('ReceiveModeRequest', '__init__', 'Error finding available receive mode directory') + self.upload_error = True + break + except PermissionError: + self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { + "receive_mode_dir": self.receive_mode_dir + }) + print(strings._('error_cannot_create_data_dir').format(self.receive_mode_dir)) + self.web.common.log('ReceiveModeRequest', '__init__', 'Permission denied creating receive mode directory') + self.upload_error = True + + # If there's an error so far, finish early + if self.upload_error: + return + # A dictionary that maps filenames to the bytes uploaded so far self.progress = {} @@ -331,7 +321,11 @@ class ReceiveModeRequest(Request): 'complete': False } - return ReceiveModeTemporaryFile(self, filename, self.file_write_func, self.file_close_func) + f = ReceiveModeFile(self, filename, self.file_write_func, self.file_close_func) + if f.upload_error: + self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file') + self.upload_error = True + return f def close(self): """ @@ -376,8 +370,6 @@ class ReceiveModeRequest(Request): self.progress[filename]['uploaded_bytes'] += length if self.previous_file != filename: - if self.previous_file is not None: - print('') self.previous_file = filename print('\r=> {:15s} {}'.format( diff --git a/onionshare/web/web.py b/onionshare/web/web.py index e2b22c4d..183c512c 100644 --- a/onionshare/web/web.py +++ b/onionshare/web/web.py @@ -14,7 +14,7 @@ from flask import Flask, request, render_template, abort, make_response, __versi from .. import strings from .share_mode import ShareModeWeb -from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeTemporaryFile, ReceiveModeRequest +from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest # Stub out flask's show_server_banner function, to avoiding showing warnings that -- cgit v1.2.3-54-g00ecf From 5901866200ca9e76288cf0bcbb034e4e6c3ce7af Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Feb 2019 23:05:51 -0800 Subject: Call secure_filename on the filename first thing, so we don't end up working with multiple versions of the same filename --- onionshare/web/receive_mode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index be68900e..15b79486 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -158,10 +158,11 @@ class ReceiveModeFile(object): self.onionshare_write_func = write_func self.onionshare_close_func = close_func - self.filename = os.path.join(self.onionshare_request.receive_mode_dir, secure_filename(filename)) + self.filename = os.path.join(self.onionshare_request.receive_mode_dir, filename) self.filename_in_progress = '{}.part'.format(self.filename) # Open the file + self.upload_error = False try: self.f = open(self.filename_in_progress, 'wb+') except: @@ -316,6 +317,8 @@ class ReceiveModeRequest(Request): self.told_gui_about_request = True + filename = secure_filename(filename) + self.progress[filename] = { 'uploaded_bytes': 0, 'complete': False -- cgit v1.2.3-54-g00ecf From dafe57cdef76a20d11e33c521fcfc06161dcb0c0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 11 Feb 2019 23:23:31 -0800 Subject: When you hit an upload_error, alert the user --- onionshare/web/receive_mode.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 15b79486..1550fab8 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -82,7 +82,14 @@ class ReceiveModeWeb(object): if request.upload_error: self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error') + + self.web.add_request(self.web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE, request.path, { + "receive_mode_dir": request.receive_mode_dir + }) + print(strings._('error_cannot_create_data_dir').format(request.receive_mode_dir)) + flash('Error uploading, please inform the OnionShare user', 'error') + if self.common.settings.get('public_mode'): return redirect('/') else: -- cgit v1.2.3-54-g00ecf From 28eaf2f2beac99e4a217cff53514a935384f744d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Feb 2019 14:30:41 -0800 Subject: Fix typo in comment --- onionshare/web/receive_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 1550fab8..b245042b 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -173,7 +173,7 @@ class ReceiveModeFile(object): try: self.f = open(self.filename_in_progress, 'wb+') except: - # This will only happen if someone is messaging with the data dir while + # This will only happen if someone is messing with the data dir while # OnionShare is running, but if it does make sure to throw an error self.upload_error = True self.f = tempfile.TemporaryFile('wb+') -- cgit v1.2.3-54-g00ecf From 09c2696c27eb9a8bbf7a7d639530de64a4526c70 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Feb 2019 14:36:14 -0800 Subject: Gracefully handle exceptions while writing a file during a receive mode transfer (like out of disk space error) --- onionshare/web/receive_mode.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index b245042b..ea1dc2fd 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -193,10 +193,15 @@ class ReceiveModeFile(object): """ if self.upload_error or (not self.onionshare_request.stop_q.empty()): self.close() - self.onionshare_request.close() + self.onionshare_request.close(self.upload_error) return - bytes_written = self.f.write(b) + try: + bytes_written = self.f.write(b) + except: + # If we can't write the file, close early + self.upload_error = True + return self.onionshare_write_func(self.onionshare_filename, bytes_written) def close(self): @@ -394,8 +399,12 @@ class ReceiveModeRequest(Request): 'progress': self.progress }) - def file_close_func(self, filename): + def file_close_func(self, filename, upload_error=False): """ This function gets called when a specific file is closed. """ self.progress[filename]['complete'] = True + + # If the file tells us there was an upload error, let the request know as well + if upload_error: + self.upload_error = True -- cgit v1.2.3-54-g00ecf From e625d3b009e9cc3126813c0cd74b9d804e1a201e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Feb 2019 15:54:25 -0800 Subject: Catch more exceptions in ReceiveModeFile that trigger on a full disk --- onionshare/web/receive_mode.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index ea1dc2fd..2c37998c 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -198,21 +198,24 @@ class ReceiveModeFile(object): try: bytes_written = self.f.write(b) + self.onionshare_write_func(self.onionshare_filename, bytes_written) + except: - # If we can't write the file, close early self.upload_error = True - return - self.onionshare_write_func(self.onionshare_filename, bytes_written) def close(self): """ Custom close method that calls out to onionshare_close_func """ - self.f.close() + try: + self.f.close() - if not self.upload_error: - # Rename the in progress file to the final filename - os.rename(self.filename_in_progress, self.filename) + if not self.upload_error: + # Rename the in progress file to the final filename + os.rename(self.filename_in_progress, self.filename) + + except: + self.upload_error = True self.onionshare_close_func(self.onionshare_filename) -- cgit v1.2.3-54-g00ecf From 1dbd82f74a72c8e786c798219c93a15efb8c9df1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 12 Feb 2019 16:02:19 -0800 Subject: Oops, was passing self.upload_error into the wrong close function --- onionshare/web/receive_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 2c37998c..566946f2 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -193,7 +193,7 @@ class ReceiveModeFile(object): """ if self.upload_error or (not self.onionshare_request.stop_q.empty()): self.close() - self.onionshare_request.close(self.upload_error) + self.onionshare_request.close() return try: @@ -217,7 +217,7 @@ class ReceiveModeFile(object): except: self.upload_error = True - self.onionshare_close_func(self.onionshare_filename) + self.onionshare_close_func(self.onionshare_filename, self.upload_error) class ReceiveModeRequest(Request): -- cgit v1.2.3-54-g00ecf From 6c36fcb9539db1adf1ec47223e476c81290420d2 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 13 Feb 2019 12:49:42 +1100 Subject: Use Ajax to send files in receive mode to workaround browser bug with large files --- share/static/js/receive.js | 43 +++++++++++++++++++++++++++++++++++++++++++ share/templates/receive.html | 7 ++++--- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 share/static/js/receive.js diff --git a/share/static/js/receive.js b/share/static/js/receive.js new file mode 100644 index 00000000..ed4d87e0 --- /dev/null +++ b/share/static/js/receive.js @@ -0,0 +1,43 @@ +var form = document.getElementById('send'); +var fileSelect = document.getElementById('file-select'); +var uploadButton = document.getElementById('send-button'); + +form.onsubmit = function(event) { + event.preventDefault(); + + // Update button text. + uploadButton.innerHTML = 'Uploading...'; + + // Get the selected files from the input. + var files = fileSelect.files; + + // Create a new FormData object. + var formData = new FormData(); + + // Loop through each of the selected files. + for (var i = 0; i < files.length; i++) { + var file = files[i]; + + // Add the file to the request. + formData.append('file[]', file, file.name); + } + + // Set up the request. + var xhr = new XMLHttpRequest(); + + // Open the connection. + xhr.open('POST', window.location.pathname + '/upload', true); + + xhr.onload = function() { + if (xhr.status == 200) { + uploadButton.innerHTML = 'Send Files'; + if (document.getElementByClassName('flashes') !=null) + var flashes = document.getElementByClassName('flashes') + } + } + } + + // Send the Data. + xhr.send(formData); +} + diff --git a/share/templates/receive.html b/share/templates/receive.html index e85b6ff9..5d2741ed 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -17,9 +17,9 @@

    Send Files

    Select the files you want to send, then click "Send Files"...

    -
    -

    -

    + +

    +

    {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} @@ -34,5 +34,6 @@
    + -- cgit v1.2.3-54-g00ecf From 044c4d0e6e27d9877482278a4bf4941bb4c42665 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 13 Feb 2019 13:53:03 +1100 Subject: fix missing bracket --- share/static/js/receive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index ed4d87e0..f247b4e4 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -31,7 +31,7 @@ form.onsubmit = function(event) { xhr.onload = function() { if (xhr.status == 200) { uploadButton.innerHTML = 'Send Files'; - if (document.getElementByClassName('flashes') !=null) + if (document.getElementByClassName('flashes') !=null) { var flashes = document.getElementByClassName('flashes') } } -- cgit v1.2.3-54-g00ecf From 54e0b6ffdaa6e2030f20d03dabdc1c28903fe905 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 13 Feb 2019 14:10:27 +1100 Subject: remove code that doesn't work anyway --- share/static/js/receive.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index f247b4e4..ae915865 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -31,9 +31,6 @@ form.onsubmit = function(event) { xhr.onload = function() { if (xhr.status == 200) { uploadButton.innerHTML = 'Send Files'; - if (document.getElementByClassName('flashes') !=null) { - var flashes = document.getElementByClassName('flashes') - } } } -- cgit v1.2.3-54-g00ecf From 105329fa28195f02751da4dd29c575bde7d7160a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Feb 2019 09:19:04 -0800 Subject: Add noscript warning --- share/static/css/style.css | 10 ++++++++++ share/templates/receive.html | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/share/static/css/style.css b/share/static/css/style.css index fd10ecdf..45fb0f46 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -192,3 +192,13 @@ li.info { color: #666666; margin: 0 0 20px 0; } + +div.noscript { + border: 1px solid #e55454; + text-align: left; + color: #e55454; + padding: 1em; + line-height: 150%; + max-width: 700px; + margin: 100px 2em 0 2em; +} diff --git a/share/templates/receive.html b/share/templates/receive.html index 5d2741ed..679a3234 100644 --- a/share/templates/receive.html +++ b/share/templates/receive.html @@ -32,6 +32,15 @@ {% endwith %} + + -- cgit v1.2.3-54-g00ecf From 5755cd625b3eda25c9eaad451ab83ba961d7b1ea Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Feb 2019 09:56:36 -0800 Subject: Starting to implement upload progress --- share/static/js/receive.js | 58 +++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index ae915865..8047f987 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -5,36 +5,46 @@ var uploadButton = document.getElementById('send-button'); form.onsubmit = function(event) { event.preventDefault(); - // Update button text. - uploadButton.innerHTML = 'Uploading...'; + // Disable button, and update text + uploadButton.disabled = true; + uploadButton.innerHTML = 'Uploading ...'; - // Get the selected files from the input. + // Create form data var files = fileSelect.files; - - // Create a new FormData object. var formData = new FormData(); - - // Loop through each of the selected files. for (var i = 0; i < files.length; i++) { var file = files[i]; - - // Add the file to the request. formData.append('file[]', file, file.name); } - // Set up the request. - var xhr = new XMLHttpRequest(); - - // Open the connection. - xhr.open('POST', window.location.pathname + '/upload', true); - - xhr.onload = function() { - if (xhr.status == 200) { - uploadButton.innerHTML = 'Send Files'; - } - } - - // Send the Data. - xhr.send(formData); + // Set up the request + var ajax = new XMLHttpRequest(); + + ajax.upload.addEventListener('progress', function(event){ + console.log('upload progress', 'uploaded '+event.loaded+' bytes / '+event.total+' bytes'); + var percent = Math.ceil(event.loaded / event.total) * 100; + uploadButton.innerHTML = 'Uploading '+percent+'%'; + }, false); + + ajax.addEventListener("load", function(event){ + console.log("upload finished"); + if(ajax.status == 200) { + // Re-enable button, and update text + uploadButton.innerHTML = 'Send Files'; + uploadButton.disabled = false; + } + }, false); + + ajax.addEventListener("error", function(event){ + console.log('error', event); + }, false); + + ajax.addEventListener("abort", function(event){ + console.log('abort', event); + }, false); + + // Send the request + ajax.open('POST', window.location.pathname + '/upload', true); + ajax.send(formData); + console.log("upload started"); } - -- cgit v1.2.3-54-g00ecf From 1c16b092a3c6c433326c82fde72c6bb0c59522ae Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 13 Feb 2019 17:40:33 -0800 Subject: Use a div that gets hidden, instead of a
    Sender sharing files with OnionShareSharing files with OnionShare Receiver downloading files with Tor BrowserDownloading OnionShare files using Tor BrowserReceiving files with OnionShareUploading files to OnionShare user using Tor Browser
    ","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" + + -- cgit v1.2.3-54-g00ecf From 9b35ca9bede4cb11c124140f90f0bc2f09b4a6eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 17 Feb 2019 14:07:38 +1100 Subject: Don't clear in-progress transfers from the History list --- onionshare_gui/mode/history.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 6af804b2..913ed164 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -237,6 +237,7 @@ class ReceiveHistoryItem(HistoryItem): self.id = id self.content_length = content_length self.started = datetime.now() + self.status = 'started' # Label self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) @@ -313,6 +314,9 @@ class ReceiveHistoryItem(HistoryItem): self.files[data['filename']].set_dir(data['dir']) elif data['action'] == 'finished': + # Change the status + self.status = 'finished' + # Hide the progress bar self.progress_bar.hide() @@ -320,6 +324,9 @@ class ReceiveHistoryItem(HistoryItem): self.label.setText(self.get_finished_label_text(self.started)) elif data['action'] == 'canceled': + # Change the status + self.status = 'canceled' + # Hide the progress bar self.progress_bar.hide() @@ -389,11 +396,11 @@ class HistoryItemList(QtWidgets.QScrollArea): """ Reset all items, emptying the list. Override this method. """ - for item in self.items.values(): - self.items_layout.removeWidget(item) - item.close() - self.items = {} - + for key, item in self.items.copy().items(): + if item.status != 'started': + self.items_layout.removeWidget(item) + item.close() + del self.items[key] class History(QtWidgets.QWidget): """ @@ -495,15 +502,17 @@ class History(QtWidgets.QWidget): """ self.item_list.reset() - # Hide not empty, show empty - self.not_empty.hide() - self.empty.show() + if not any(self.item_list.items): + # Hide not empty, show empty + self.not_empty.hide() + self.empty.show() + # Reset in-progress counter + self.in_progress_count = 0 + self.update_in_progress() - # Reset counters + # Reset completed counter self.completed_count = 0 - self.in_progress_count = 0 self.update_completed() - self.update_in_progress() def update_completed(self): """ -- cgit v1.2.3-54-g00ecf From 51e98f40e717a4413427ef3aab4da9839c7c6bea Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 17 Feb 2019 15:49:37 +1100 Subject: Update the GUI when the browser has canceled an upload in Receive Mode. Don't increment the completed counter --- onionshare/web/receive_mode.py | 8 ++++---- onionshare_gui/mode/receive_mode/__init__.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index 566946f2..a0cf5934 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -332,14 +332,14 @@ class ReceiveModeRequest(Request): self.told_gui_about_request = True - filename = secure_filename(filename) + self.filename = secure_filename(filename) - self.progress[filename] = { + self.progress[self.filename] = { 'uploaded_bytes': 0, 'complete': False } - f = ReceiveModeFile(self, filename, self.file_write_func, self.file_close_func) + f = ReceiveModeFile(self, self.filename, self.file_write_func, self.file_close_func) if f.upload_error: self.web.common.log('ReceiveModeRequest', '_get_file_stream', 'Error creating file') self.upload_error = True @@ -362,7 +362,7 @@ class ReceiveModeRequest(Request): if self.told_gui_about_request: upload_id = self.upload_id - if not self.web.stop_q.empty(): + if not self.web.stop_q.empty() or not self.progress[self.filename]['complete']: # Inform the GUI that the upload has canceled self.web.add_request(self.web.REQUEST_UPLOAD_CANCELED, self.path, { 'id': upload_id diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 3a90f2f4..5fb33ab3 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -198,9 +198,7 @@ class ReceiveMode(Mode): self.history.update(event["data"]["id"], { 'action': 'canceled' }) - self.history.completed_count += 1 self.history.in_progress_count -= 1 - self.history.update_completed() self.history.update_in_progress() def on_reload_settings(self): -- cgit v1.2.3-54-g00ecf From 759a0dc2a3fd07fcd35a2a251526d8c793be5b52 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 17 Feb 2019 16:28:11 +1100 Subject: Set status attribute for ShareHistoryItems as well --- onionshare_gui/mode/history.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 913ed164..6a3db3a5 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -90,6 +90,7 @@ class ShareHistoryItem(HistoryItem): self.downloaded_bytes = 0 self.started = time.time() self.started_dt = datetime.fromtimestamp(self.started) + self.status = 'started' # Label self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) @@ -124,6 +125,7 @@ class ShareHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) + self.status = 'finished' else: elapsed = time.time() - self.started @@ -142,6 +144,7 @@ class ShareHistoryItem(HistoryItem): def cancel(self): self.progress_bar.setFormat(strings._('gui_canceled')) + self.status = 'canceled' @property def estimated_time_remaining(self): @@ -501,8 +504,7 @@ class History(QtWidgets.QWidget): Reset all items. """ self.item_list.reset() - - if not any(self.item_list.items): + if len(self.item_list.items) == 0: # Hide not empty, show empty self.not_empty.hide() self.empty.show() -- cgit v1.2.3-54-g00ecf From 8cf8aa201d6b79805056c4844d049d9b99dd0560 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 17 Feb 2019 17:42:25 +1100 Subject: Use constants for history item status, to be consistent with other parts of the project --- onionshare_gui/mode/history.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 6a3db3a5..1546cb68 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -31,6 +31,10 @@ class HistoryItem(QtWidgets.QWidget): """ The base history item """ + STATUS_STARTED = 0 + STATUS_FINISHED = 1 + STATUS_CANCELED = 2 + def __init__(self): super(HistoryItem, self).__init__() @@ -90,7 +94,7 @@ class ShareHistoryItem(HistoryItem): self.downloaded_bytes = 0 self.started = time.time() self.started_dt = datetime.fromtimestamp(self.started) - self.status = 'started' + self.status = HistoryItem.STATUS_STARTED # Label self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) @@ -125,7 +129,7 @@ class ShareHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) - self.status = 'finished' + self.status = HistoryItem.STATUS_FINISHED else: elapsed = time.time() - self.started @@ -144,7 +148,7 @@ class ShareHistoryItem(HistoryItem): def cancel(self): self.progress_bar.setFormat(strings._('gui_canceled')) - self.status = 'canceled' + self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): @@ -240,7 +244,7 @@ class ReceiveHistoryItem(HistoryItem): self.id = id self.content_length = content_length self.started = datetime.now() - self.status = 'started' + self.status = HistoryItem.STATUS_STARTED # Label self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) @@ -318,7 +322,7 @@ class ReceiveHistoryItem(HistoryItem): elif data['action'] == 'finished': # Change the status - self.status = 'finished' + self.status = HistoryItem.STATUS_FINISHED # Hide the progress bar self.progress_bar.hide() @@ -328,7 +332,7 @@ class ReceiveHistoryItem(HistoryItem): elif data['action'] == 'canceled': # Change the status - self.status = 'canceled' + self.status = HistoryItem.STATUS_CANCELED # Hide the progress bar self.progress_bar.hide() @@ -400,7 +404,7 @@ class HistoryItemList(QtWidgets.QScrollArea): Reset all items, emptying the list. Override this method. """ for key, item in self.items.copy().items(): - if item.status != 'started': + if item.status != HistoryItem.STATUS_STARTED: self.items_layout.removeWidget(item) item.close() del self.items[key] -- cgit v1.2.3-54-g00ecf From c349e6049cb0d4386fe747b78049a7ffd655f1a4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 08:12:45 -0800 Subject: Update red and green colors to match the Qt GUI colors --- share/static/css/style.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share/static/css/style.css b/share/static/css/style.css index 65bafba5..0751d306 100644 --- a/share/static/css/style.css +++ b/share/static/css/style.css @@ -153,7 +153,7 @@ div#uploads .upload .upload-status { } div#uploads .upload input.cancel { - color: #f24537; + color: #d0011b; border: 0; background: none; box-shadow: none; @@ -188,11 +188,11 @@ ul.flashes li { } li.error { - color: #dd4040; + color: #d0011b; } li.info { - color: #3e933f; + color: #5fa416; } .closed-wrapper { -- cgit v1.2.3-54-g00ecf From 77aa909e358a6b519bcb156937b8426ea97320ad Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 09:51:19 -0800 Subject: Don't use jQuery for the ajax request, instead manually use an XMLHttpRequest in order to more easily support multiple upload divs --- share/static/js/receive.js | 202 ++++++++++++++++++++++----------------------- 1 file changed, 97 insertions(+), 105 deletions(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index 0de80952..2aca03df 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -4,11 +4,94 @@ $(function(){ $('#flashes').append($('
  • ').addClass(category).text(message)); }; - // Add an upload - var new_upload_div = function(xhr, filenames) { - /* - The DOM for an upload looks something like this: + // Intercept submitting the form + $('#send').submit(function(event){ + event.preventDefault(); + + // Create form data, and list of filenames + var files = $('#file-select').get(0).files; + var filenames = []; + var formData = new FormData(); + for(var i = 0; i < files.length; i++) { + var file = files[i]; + filenames.push(file.name); + formData.append('file[]', file, file.name); + } + + // Reset the upload form + $('#send').get(0).reset(); + + // Don't use jQuery for ajax request, because the upload progress event doesn't + // have access to the the XMLHttpRequest object + var ajax = new XMLHttpRequest(); + + ajax.upload.addEventListener('progress', function(event){ + // Update progress bar for this specific upload + if(event.lengthComputable) { + console.log('upload progress', ''+event.loaded+' bytes / '+event.total+' bytes'); + $('progress', ajax.$upload_div).attr({ + value: event.loaded, + max: event.total, + }); + } + + // If it's finished sending all data to the first Tor node, remove cancel button + // and update the status + if(event.loaded == event.total) { + console.log('upload progress', 'complete'); + $('.cancel', ajax.$upload_div).remove(); + $('.upload-status', ajax.$upload_div).html(' Waiting for data to finish traversing Tor network ...'); + } + }, false); + + ajax.addEventListener('load', function(event){ + console.log('upload finished', ajax.response); + + // Remove the upload div + ajax.$upload_div.remove(); + + // Parse response + try { + var response = JSON.parse(ajax.response); + + // The 'new_body' response replaces the whole HTML document and ends + if('new_body' in response) { + $('body').html(response['new_body']); + return; + } + + // Show error flashes + if('error_flashes' in response) { + for(var i=0; i
    @@ -16,14 +99,14 @@ $(function(){
    Sending to first Tor node ...
    - - */ + */ var $progress = $('').attr({ value: '0', max: 100 }); var $cancel_button = $('').addClass('cancel').attr({ type: 'button', value: 'Cancel' }); var $upload_filename = $('
    ').addClass('upload-filename').text(filenames.join(', ')); var $upload_status = $('
    ').addClass('upload-status').text('Sending data to initial Tor node ...'); - var $upload_div = $('
    ').addClass('upload') + var $upload_div = $('
    ') + .addClass('upload') .append( $('
    ').addClass('upload-meta') .append($cancel_button) @@ -34,107 +117,16 @@ $(function(){ $cancel_button.click(function(){ // Abort the upload, and remove the upload div - xhr.abort(); + ajax.abort(); $upload_div.remove() }); - return $upload_div; - }; - - // Intercept submitting the form - $('#send').submit(function(event){ - event.preventDefault(); - - // Create form data, and list of filenames - var files = $('#file-select').get(0).files; - var filenames = []; - var formData = new FormData(); - for(var i = 0; i < files.length; i++) { - var file = files[i]; - filenames.push(file.name); - formData.append('file[]', file, file.name); - } - - // Reset the upload form - $('#send').get(0).reset(); - - // Start upload - xhr = $.ajax({ - method: 'POST', - url: window.location.pathname + '/upload-ajax', - data: formData, - // Tell jQuery not to process data or worry about content-type - cache: false, - contentType: false, - processData: false, - // Custom XMLHttpRequest - xhr: function() { - var xhr = $.ajaxSettings.xhr(); - if(xhr.upload) { - xhr.upload.addEventListener('progress', function(event) { - // Update progress bar for this specific upload - if(event.lengthComputable) { - console.log('upload progress', ''+event.loaded+' bytes / '+event.total+' bytes'); - $('progress', this.$upload_div).attr({ - value: event.loaded, - max: event.total, - }); - } - - // If it's finished sending all data to the first Tor node, remove cancel button - // and update the status - if(event.loaded == event.total) { - console.log('upload progress', 'complete'); - $('.cancel', this.$upload_div).remove(); - $('.upload-status', this.$upload_div).html(' Waiting for data to finish traversing Tor network ...'); - } - }, false); - } - return xhr; - }, - success: function(data, textStatus, xhr){ - console.log('upload finished', data); - - // Remove the upload div - xhr.$upload_div.remove(); - - // Parse response - try { - var response = JSON.parse(data); - - // The 'new_body' response replaces the whole HTML document and ends - if('new_body' in response) { - $('body').html(response['new_body']); - return; - } - - // Show error flashes - if('error_flashes' in response) { - for(var i=0; i Date: Sun, 17 Feb 2019 09:58:57 -0800 Subject: On error, remove the upload div --- share/static/js/receive.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index 2aca03df..36bca07c 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -81,7 +81,10 @@ $(function(){ ajax.addEventListener('error', function(event){ console.log('error', event); - flash('error', 'Error uploading'); + flash('error', 'Error uploading: '+filenames.join(', ')); + + // Remove the upload div + ajax.$upload_div.remove() }, false); ajax.addEventListener('abort', function(event){ -- cgit v1.2.3-54-g00ecf From 57522c58b67b29854ad563c1cf54666966c39998 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 21:19:01 -0800 Subject: Remove console.log() debug statements --- share/static/js/receive.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/share/static/js/receive.js b/share/static/js/receive.js index 36bca07c..02c37b3f 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -28,7 +28,6 @@ $(function(){ ajax.upload.addEventListener('progress', function(event){ // Update progress bar for this specific upload if(event.lengthComputable) { - console.log('upload progress', ''+event.loaded+' bytes / '+event.total+' bytes'); $('progress', ajax.$upload_div).attr({ value: event.loaded, max: event.total, @@ -38,15 +37,12 @@ $(function(){ // If it's finished sending all data to the first Tor node, remove cancel button // and update the status if(event.loaded == event.total) { - console.log('upload progress', 'complete'); $('.cancel', ajax.$upload_div).remove(); $('.upload-status', ajax.$upload_div).html(' Waiting for data to finish traversing Tor network ...'); } }, false); ajax.addEventListener('load', function(event){ - console.log('upload finished', ajax.response); - // Remove the upload div ajax.$upload_div.remove(); @@ -74,13 +70,11 @@ $(function(){ } } } catch(e) { - console.log('invalid response'); flash('error', 'Invalid response from server: '+data); } }, false); ajax.addEventListener('error', function(event){ - console.log('error', event); flash('error', 'Error uploading: '+filenames.join(', ')); // Remove the upload div @@ -88,7 +82,6 @@ $(function(){ }, false); ajax.addEventListener('abort', function(event){ - console.log('abort', event); flash('error', 'Upload aborted: '+filenames.join(', ')); }, false); @@ -130,6 +123,5 @@ $(function(){ // Send the request ajax.open('POST', window.location.pathname + '/upload-ajax', true); ajax.send(formData); - console.log('upload started'); }); }); -- cgit v1.2.3-54-g00ecf From c0a23ead909a2bad99eb6edcc35d2c373ef548e0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 21:19:47 -0800 Subject: Reword licenses readme --- install/licenses/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/licenses/readme.txt b/install/licenses/readme.txt index c92e516d..3dd5d2fe 100644 --- a/install/licenses/readme.txt +++ b/install/licenses/readme.txt @@ -1 +1 @@ -This folder contains the software licenses for 3rd-party binaries included with OnionShare. +This folder contains 3rd-party licenses for software included with OnionShare. -- cgit v1.2.3-54-g00ecf From 2f026e8e5945b20090b42651a09e059132487b0d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 21:29:47 -0800 Subject: Update screenshots --- screenshots/appdata-onionshare-receive-client.png | Bin 392602 -> 361337 bytes screenshots/appdata-onionshare-receive-server.png | Bin 350674 -> 340924 bytes screenshots/onionshare-receive-client.png | Bin 63044 -> 67444 bytes screenshots/onionshare-receive-server.png | Bin 73910 -> 78558 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/appdata-onionshare-receive-client.png b/screenshots/appdata-onionshare-receive-client.png index 49f13adb..8edcc326 100644 Binary files a/screenshots/appdata-onionshare-receive-client.png and b/screenshots/appdata-onionshare-receive-client.png differ diff --git a/screenshots/appdata-onionshare-receive-server.png b/screenshots/appdata-onionshare-receive-server.png index af2f3659..121eab48 100644 Binary files a/screenshots/appdata-onionshare-receive-server.png and b/screenshots/appdata-onionshare-receive-server.png differ diff --git a/screenshots/onionshare-receive-client.png b/screenshots/onionshare-receive-client.png index 82beb23f..be3f3585 100644 Binary files a/screenshots/onionshare-receive-client.png and b/screenshots/onionshare-receive-client.png differ diff --git a/screenshots/onionshare-receive-server.png b/screenshots/onionshare-receive-server.png index 798e49eb..283f4c41 100644 Binary files a/screenshots/onionshare-receive-server.png and b/screenshots/onionshare-receive-server.png differ -- cgit v1.2.3-54-g00ecf From 879cf687b16e4bf67ba114a8e9f68851938744e1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 17 Feb 2019 21:37:15 -0800 Subject: Update share mode screenshots too --- screenshots/appdata-onionshare-share-client.png | Bin 407909 -> 372511 bytes screenshots/appdata-onionshare-share-server.png | Bin 528447 -> 526914 bytes screenshots/onionshare-share-client.png | Bin 42423 -> 43404 bytes screenshots/onionshare-share-server.png | Bin 55027 -> 57758 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/appdata-onionshare-share-client.png b/screenshots/appdata-onionshare-share-client.png index 08cb864c..c57fb2a8 100644 Binary files a/screenshots/appdata-onionshare-share-client.png and b/screenshots/appdata-onionshare-share-client.png differ diff --git a/screenshots/appdata-onionshare-share-server.png b/screenshots/appdata-onionshare-share-server.png index 83cc1951..d85e45ce 100644 Binary files a/screenshots/appdata-onionshare-share-server.png and b/screenshots/appdata-onionshare-share-server.png differ diff --git a/screenshots/onionshare-share-client.png b/screenshots/onionshare-share-client.png index 7cb6c3c8..58f102d4 100644 Binary files a/screenshots/onionshare-share-client.png and b/screenshots/onionshare-share-client.png differ diff --git a/screenshots/onionshare-share-server.png b/screenshots/onionshare-share-server.png index 76830876..329fc8af 100644 Binary files a/screenshots/onionshare-share-server.png and b/screenshots/onionshare-share-server.png differ -- cgit v1.2.3-54-g00ecf From 3e2901ad8c17a2cff59d2a3623ebe1a96164aeb5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 18 Feb 2019 10:57:20 -0800 Subject: Fix minor bugs: When making the ajax POST request, strip the trailing slash from window.location.pathname so the URL works in public mode; and add a newline before printing message when receiving file --- onionshare/web/receive_mode.py | 2 +- share/static/js/receive.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/onionshare/web/receive_mode.py b/onionshare/web/receive_mode.py index fa9909ae..d6ef86ad 100644 --- a/onionshare/web/receive_mode.py +++ b/onionshare/web/receive_mode.py @@ -79,7 +79,7 @@ class ReceiveModeWeb(object): }) self.common.log('ReceiveModeWeb', 'define_routes', '/upload, uploaded {}, saving to {}'.format(f.filename, local_path)) - print(strings._('receive_mode_received_file').format(local_path)) + print('\n' + strings._('receive_mode_received_file').format(local_path)) if request.upload_error: self.common.log('ReceiveModeWeb', 'define_routes', '/upload, there was an upload error') diff --git a/share/static/js/receive.js b/share/static/js/receive.js index 02c37b3f..c29c726c 100644 --- a/share/static/js/receive.js +++ b/share/static/js/receive.js @@ -121,7 +121,7 @@ $(function(){ $('#uploads').append($upload_div); // Send the request - ajax.open('POST', window.location.pathname + '/upload-ajax', true); + ajax.open('POST', window.location.pathname.replace(/\/$/, '') + '/upload-ajax', true); ajax.send(formData); }); }); -- cgit v1.2.3-54-g00ecf From 8e200cd8b031225e13a309359142ec4442f772a0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 18 Feb 2019 12:28:02 -0800 Subject: Prevent crashing when cleaning up file, if file is still in use --- onionshare/onionshare.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/onionshare/onionshare.py b/onionshare/onionshare.py index 32e56ba0..551b8314 100644 --- a/onionshare/onionshare.py +++ b/onionshare/onionshare.py @@ -95,10 +95,14 @@ class OnionShare(object): """ self.common.log('OnionShare', 'cleanup') - # cleanup files - for filename in self.cleanup_filenames: - if os.path.isfile(filename): - os.remove(filename) - elif os.path.isdir(filename): - shutil.rmtree(filename) + # Cleanup files + try: + for filename in self.cleanup_filenames: + if os.path.isfile(filename): + os.remove(filename) + elif os.path.isdir(filename): + shutil.rmtree(filename) + except: + # Don't crash if file is still in use + pass self.cleanup_filenames = [] -- cgit v1.2.3-54-g00ecf