From 6c91d8977ac607a42f6245d62a8ad74c3d336418 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 19:51:51 -0700 Subject: [PATCH 001/126] 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 4c69de072a9257687b736caeb933c874a971b47f..ec35400a174af4f7117b000982aaf28d78c64263 100644 GIT binary patch delta 371 zcmV-(0gV2e1G@u|QGenD7y>O8AP@3M00033@$Spa7JBJD?VdZ3YPq z1|#IGNsMF!#OrDwZUXF1N^SzB?&WG?Xbjbe z*tyfhgS@1o^K1mSfO3y#yOKtn(x(j=mDF(ND=j*b=5l7%l2i^Z-%|*D2Imtqd+qh6 zmaL`Z;U79)T?3QAEN}rlr~QGhB4UqO{==t8%oWTJ5WJW4BI!Fho1_~r;RiYe;ohxS R&MEq^`O?b2K-npA(ik&?3?9A*l zOWM})o%7{XLVF#;iAZd;6~f_2Y)FH4g>#Ic8^@7$Gv7uR#(!~vTApk%m++V%E5giX zWIcFmfS{hgpwfX=%p^wfioMX*B+3Q2GVWnMaqB@Ov08xr#8d_vz*D4!RMo$+4`FnF zY%TJVMYfy6ZpL(2*1Wyv1HBO${HNvTph++t1{xc5H9~fnc*KoT*o>kTT*3nO!+8ar ziOTz-654B3*i0W7%$VnzSiQW=cJk%cf{%s8-%603qAyf*gxf~ta1tTAZsk9`iNunG lr8 Date: Mon, 23 Apr 2018 21:08:03 -0700 Subject: [PATCH 002/126] 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() From ac13790673fa7cb89eca5cf818a338e57ad37d81 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:15:30 -0700 Subject: [PATCH 003/126] Flip between modes when clicking mode buttons, and some css --- onionshare_gui/onionshare_gui.py | 37 +++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 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; - } + self.status_bar.setStyleSheet(""" + QStatusBar { + font-style: italic; + color: #666666; + } - QStatusBar::item { - border: 0px; - }""" - - self.status_bar.setStyleSheet(statusBar_cssStyleData) + 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() From 86fa0215d8554b40bf84648ed209cf159b94555d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:16:10 -0700 Subject: [PATCH 004/126] 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: From b349471c308f1c3055667ec12d9c33a6c444cf41 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:24:12 -0700 Subject: [PATCH 005/126] 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 From ac67f6be6a848a545d58b91e6ed4720d40ad5bc5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 21:34:29 -0700 Subject: [PATCH 006/126] 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 From 9b2b8155258dbff7db0ed8532aae6a2d550d2e66 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 23 Apr 2018 22:08:51 -0700 Subject: [PATCH 007/126] 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) From bda82bc7a00f1dcce7c3ae55054a4958ff139c68 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 08:48:17 -0700 Subject: [PATCH 008/126] 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: From a232cfdbded439e30bb95d868fcaf387f7209acb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 08:51:39 -0700 Subject: [PATCH 009/126] 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): From 1d7ec585eeb9a15d9dc74eba35ea1917073c8536 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 09:21:23 -0700 Subject: [PATCH 010/126] 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() From 2ee7e74236c0ef57cd256a56bd110bcb58228f2a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 09:26:06 -0700 Subject: [PATCH 011/126] 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 From b6b61f753d6ac97ffcb2cbe5bd6300a405410947 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 10:07:59 -0700 Subject: [PATCH 012/126] 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 From a017af0748cd008ce7da0cdd0e11003a0cbaf45d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 24 Apr 2018 17:26:54 -0700 Subject: [PATCH 013/126] Make ShareMode and ReceiveMode directories, and move ShareMode modules into its dir --- onionshare_gui/{receive_mode.py => receive_mode/__init__.py} | 0 onionshare_gui/{share_mode.py => share_mode/__init__.py} | 4 ++-- onionshare_gui/{ => share_mode}/downloads.py | 1 - onionshare_gui/{ => share_mode}/file_selection.py | 3 ++- onionshare_gui/{ => share_mode}/server_status.py | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) rename onionshare_gui/{receive_mode.py => receive_mode/__init__.py} (100%) rename onionshare_gui/{share_mode.py => share_mode/__init__.py} (99%) rename onionshare_gui/{ => share_mode}/downloads.py (99%) rename onionshare_gui/{ => share_mode}/file_selection.py (99%) rename onionshare_gui/{ => share_mode}/server_status.py (99%) diff --git a/onionshare_gui/receive_mode.py b/onionshare_gui/receive_mode/__init__.py similarity index 100% rename from onionshare_gui/receive_mode.py rename to onionshare_gui/receive_mode/__init__.py diff --git a/onionshare_gui/share_mode.py b/onionshare_gui/share_mode/__init__.py similarity index 99% rename from onionshare_gui/share_mode.py rename to onionshare_gui/share_mode/__init__.py index 008fb910..69af6eba 100644 --- a/onionshare_gui/share_mode.py +++ b/onionshare_gui/share_mode/__init__.py @@ -29,8 +29,8 @@ 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 +from ..onion_thread import OnionThread +from ..alert import Alert class ShareMode(QtWidgets.QWidget): diff --git a/onionshare_gui/downloads.py b/onionshare_gui/share_mode/downloads.py similarity index 99% rename from onionshare_gui/downloads.py rename to onionshare_gui/share_mode/downloads.py index 36d58790..31298370 100644 --- a/onionshare_gui/downloads.py +++ b/onionshare_gui/share_mode/downloads.py @@ -18,7 +18,6 @@ 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 diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/share_mode/file_selection.py similarity index 99% rename from onionshare_gui/file_selection.py rename to onionshare_gui/share_mode/file_selection.py index e6123669..13efba24 100644 --- a/onionshare_gui/file_selection.py +++ b/onionshare_gui/share_mode/file_selection.py @@ -19,10 +19,11 @@ along with this program. If not, see . """ import os from PyQt5 import QtCore, QtWidgets, QtGui -from .alert import Alert 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 diff --git a/onionshare_gui/server_status.py b/onionshare_gui/share_mode/server_status.py similarity index 99% rename from onionshare_gui/server_status.py rename to onionshare_gui/share_mode/server_status.py index 2e7310f8..e74c41a2 100644 --- a/onionshare_gui/server_status.py +++ b/onionshare_gui/share_mode/server_status.py @@ -18,11 +18,12 @@ 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 +from ..alert import Alert + class ServerStatus(QtWidgets.QWidget): """ The server status chunk of the GUI. From dd7d97dbbbe40fad79a733afccb1a430f8524ca3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 08:43:40 -0700 Subject: [PATCH 014/126] Allow changing downloads_dir from SettingsDialog --- onionshare_gui/__init__.py | 2 +- 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/{alert.py => widgets.py} | 25 ++++++++++++++ share/locale/en.json | 5 ++- 9 files changed, 71 insertions(+), 28 deletions(-) rename onionshare_gui/{alert.py => widgets.py} (58%) 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/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/alert.py b/onionshare_gui/widgets.py similarity index 58% rename from onionshare_gui/alert.py rename to onionshare_gui/widgets.py index 94886d17..eaa5904d 100644 --- a/onionshare_gui/alert.py +++ b/onionshare_gui/widgets.py @@ -38,3 +38,28 @@ class Alert(QtWidgets.QMessageBox): 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" } From f1495308347411907bb264af8076350b2d9134f2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 08:49:43 -0700 Subject: [PATCH 015/126] 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() From edd5d4f78cd08e706cebd5f66008181630e1a446 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:08:50 -0700 Subject: [PATCH 016/126] 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)) From 10581b142110d62026d0ea7d41ec631cc980a932 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:13:05 -0700 Subject: [PATCH 017/126] 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: From 2fc4330ee49749ed7925281d1690a2f09cb35257 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 09:46:49 -0700 Subject: [PATCH 018/126] 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 +++++++++++++- .../{share_mode => }/server_status.py | 9 +++-- onionshare_gui/share_mode/__init__.py | 2 +- share/locale/en.json | 9 +++-- 5 files changed, 59 insertions(+), 19 deletions(-) rename onionshare_gui/{share_mode => }/server_status.py (98%) 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/share_mode/server_status.py b/onionshare_gui/server_status.py similarity index 98% rename from onionshare_gui/share_mode/server_status.py rename to onionshare_gui/server_status.py index 4df3de79..ad16731c 100644 --- a/onionshare_gui/share_mode/server_status.py +++ b/onionshare_gui/server_status.py @@ -22,7 +22,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ..widgets import Alert +from .widgets import Alert class ServerStatus(QtWidgets.QWidget): """ @@ -39,7 +39,7 @@ class ServerStatus(QtWidgets.QWidget): STATUS_WORKING = 1 STATUS_STARTED = 2 - def __init__(self, common, qtapp, app, web, file_selection): + def __init__(self, common, qtapp, app, web, file_selection=None): super(ServerStatus, self).__init__() self.common = common @@ -49,6 +49,8 @@ class ServerStatus(QtWidgets.QWidget): self.qtapp = qtapp self.app = app self.web = web + + # Only used in share mode self.file_selection = file_selection # Shutdown timeout layout @@ -172,7 +174,8 @@ class ServerStatus(QtWidgets.QWidget): 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: + + if self.file_selection and self.file_selection.get_num_files() == 0: self.server_button.hide() else: self.server_button.show() 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/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", From 996f1d3a818a65b2a970df0238b7d3c0ba5d8c79 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:14:27 -0700 Subject: [PATCH 019/126] 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", From 81382318dcbc49161fbd02f8b7dcae73514e8164 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:22:29 -0700 Subject: [PATCH 020/126] 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) From df346ad0ab21c7659d573b9801322f9cac27c6f4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 20:50:56 -0700 Subject: [PATCH 021/126] 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", From 691db6343d46e75f63957a5cb08374254fedbb7e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 21:54:28 -0700 Subject: [PATCH 022/126] 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() From 4a1995ef559f7834e25c8300c101304f15ec4c2e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 22:14:23 -0700 Subject: [PATCH 023/126] 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 From 4c6b37988924f1273dd14ce4197148cc1b4a40d9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 22:59:26 -0700 Subject: [PATCH 024/126] Split out customization of Mode into _custom() functions, and implement those customizations in ShareMode --- onionshare_gui/mode.py | 214 ++++++++---------------- 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, 254 insertions(+), 161 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') + self.common.log('Mode', '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.start_server_step2_custom() - self._zip_progress_bar.total_files_size = Mode._compute_total_size(self.filenames) - self.status_bar.insertWidget(0, self._zip_progress_bar) + # Nothing to do here. - # 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() + # 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. This displays the large filesize - warning, if applicable. + Step 3 in starting the onionshare server. """ - self.common.log('ShareMode', 'start_server_step3') + self.common.log('Mode', '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() + 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.stop_server_custom() - 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.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) From 4050977899d0fac3c2e6894745371b23a245e626 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Wed, 25 Apr 2018 23:03:57 -0700 Subject: [PATCH 025/126] 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() From 9e9f65572ba06b43c916b1a43eb46f42b88fcfe8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 09:30:53 -0700 Subject: [PATCH 026/126] 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() From ff55d7df75cc5f61326debca9691c7f5ba56d0fd Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 10:59:38 -0700 Subject: [PATCH 027/126] 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 - done = False - while not done: - try: - r = self.web.q.get(False) - events.append(r) - except queue.Empty: - done = True + events = [] - for event in events: - if event["type"] == Web.REQUEST_LOAD: - self.share_mode.handle_request_load(event) + done = False + while not done: + try: + r = web.q.get(False) + events.append(r) + except queue.Empty: + done = True - elif event["type"] == Web.REQUEST_DOWNLOAD: - self.share_mode.handle_request_download(event) + for event in events: + if event["type"] == Web.REQUEST_LOAD: + self.share_mode.handle_request_load(event) - elif event["type"] == Web.REQUEST_RATE_LIMIT: - self.share_mode.handle_request_rate_limit(event) + elif event["type"] == Web.REQUEST_DOWNLOAD: + self.share_mode.handle_request_download(event) - elif event["type"] == Web.REQUEST_PROGRESS: - self.share_mode.handle_request_progress(event) + elif event["type"] == Web.REQUEST_RATE_LIMIT: + self.share_mode.handle_request_rate_limit(event) - elif event["type"] == Web.REQUEST_CANCELED: - self.share_mode.handle_request_canceled(event) + elif event["type"] == Web.REQUEST_PROGRESS: + self.share_mode.handle_request_progress(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["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(web.error404_count, strings._('other_page_loaded', True), event["path"])) if self.mode == self.MODE_SHARE: self.share_mode.timer_callback() From c6a2cab529c173ff249786e8dc4495ddc55ba5aa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 26 Apr 2018 11:00:59 -0700 Subject: [PATCH 028/126] 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 From 87d93c097f1cf2a4e64c38e8213c4142d28b0c33 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 22:20:12 -0700 Subject: [PATCH 029/126] 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() From 2e4db9eb31df5bb4ca47863e2316d622a6f3f2aa Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 22:32:20 -0700 Subject: [PATCH 030/126] 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() From 0996e8c0647afc564867ab77607282eda41c1dc4 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 23:02:04 -0700 Subject: [PATCH 031/126] 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", From 406515085e4f55c48230584c1fa21a6128b2619e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 27 Apr 2018 23:19:46 -0700 Subject: [PATCH 032/126] 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() From 74a799f0c11adcc96041db18019960d21e8d74e6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 12:03:10 -0700 Subject: [PATCH 033/126] 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): """ From c1413ad7da71b1c271657c0f442d770887c5ae30 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:41:15 -0700 Subject: [PATCH 034/126] 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): """ From 5d037a78fa38334d2ff6ba13f2cbd13ecb16429f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:48:31 -0700 Subject: [PATCH 035/126] 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: From 1456361566750b9e926de8e48b5128bb85267c4e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 13:59:36 -0700 Subject: [PATCH 036/126] 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 From 1a4aaa70fa7549520da837fcfdc9f63e0e38c3f8 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 15:00:23 -0700 Subject: [PATCH 037/126] 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 From 2a7f6e0d5a1d1415a2c9b076f5ff53d5463eb6c0 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 15:23:57 -0700 Subject: [PATCH 038/126] 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.", From 6632a4b426aaaef0eb7ecf01a58106ead5388e52 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 28 Apr 2018 21:08:53 -0700 Subject: [PATCH 039/126] 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)" } From 996df24646803fde0b21bf0b32646141bf2c5c2d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 15:34:11 -0700 Subject: [PATCH 040/126] 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 %} From 6cfb7026da8c3dc8be0f966e710068f60f6dd998 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 15:49:18 -0700 Subject: [PATCH 041/126] 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" } From 4f89082f185d2f2132911ef24700f4d616208037 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:33:48 -0700 Subject: [PATCH 042/126] 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 From 9a076635c5eccf74f9843f7ca4d92c9ad933049c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:41:05 -0700 Subject: [PATCH 043/126] 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" } From b6a15cf6c77c384686210a3cfecb677b9c17bf84 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:44:45 -0700 Subject: [PATCH 044/126] 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")) From 73efcf81fc681a7fe0cbe04ddd7a566ec243c759 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 16:47:36 -0700 Subject: [PATCH 045/126] 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): From 70385dd6acef35227e3329c582e0e5bd92936cff Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 17:51:58 -0700 Subject: [PATCH 046/126] 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( From 7ec993d2e0c9ddfcc5b38da32926b9711d66057f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 18:00:10 -0700 Subject: [PATCH 047/126] 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: From 86f1fb223ea8346b1e9150588f238e9945e38277 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 29 Apr 2018 18:19:00 -0700 Subject: [PATCH 048/126] 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: From c0904b1b8db758c0fe7115c847d0242955fc1b79 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 30 Apr 2018 10:01:23 -0700 Subject: [PATCH 049/126] 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(): From 65dff32702a33ae6f968f907003dacaf307db4e5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 3 May 2018 09:14:16 -0700 Subject: [PATCH 050/126] 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: From 07152ad96929b963e0ade82a86e21774e5fed8da Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Thu, 3 May 2018 09:29:54 -0700 Subject: [PATCH 051/126] 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): From 3cdd546bd3aa2b274c65f61445d70fa578c8470d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 15:40:22 -0700 Subject: [PATCH 052/126] 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 From ed28fdf123867dfda2b6aa4016e436d8e9bd1069 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 15:53:34 -0700 Subject: [PATCH 053/126] 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. From 30c9f50d2ee611267970b758c1a112fa3292b409 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:06:24 -0700 Subject: [PATCH 054/126] 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()) From dcea4595807e5bb6c158620133f2b7fac54be64e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:26:54 -0700 Subject: [PATCH 055/126] 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) From e32e850548b97152d335882917b192b90ea96641 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:35:32 -0700 Subject: [PATCH 056/126] 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) From 7b25ae1d6b66786aa6cecc5bc99d3bdcae8ac408 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:43:30 -0700 Subject: [PATCH 057/126] 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) From 3f624a4a27c4a41ab20f8111359de4896e08a7c2 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 16:57:17 -0700 Subject: [PATCH 058/126] 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 | 43 +++++++++++++------------- 2 files changed, 23 insertions(+), 24 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.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() - self.downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True)) - self.downloads_label.setStyleSheet('QLabel { font-weight: bold; font-size 14px; text-align: center; }') + 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()) From c77eba7f23bce6147023ac5580621d6c5ca45e5c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 17:33:18 -0700 Subject: [PATCH 059/126] 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", From be36f3a4b6ab7a7b2920a5ced60a82d0bce51bda Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 17:57:30 -0700 Subject: [PATCH 060/126] 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 ++--- ...load_completed.png => share_completed.png} | Bin ...eted_none.png => share_completed_none.png} | Bin ..._in_progress.png => share_in_progress.png} | Bin ...ss_none.png => share_in_progress_none.png} | Bin 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 ++- 10 files changed, 52 insertions(+), 18 deletions(-) rename share/images/{download_completed.png => share_completed.png} (100%) rename share/images/{download_completed_none.png => share_completed_none.png} (100%) rename share/images/{download_in_progress.png => share_in_progress.png} (100%) rename share/images/{download_in_progress_none.png => share_in_progress_none.png} (100%) 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/share_completed.png similarity index 100% rename from share/images/download_completed.png rename to share/images/share_completed.png diff --git a/share/images/download_completed_none.png b/share/images/share_completed_none.png similarity index 100% rename from share/images/download_completed_none.png rename to share/images/share_completed_none.png diff --git a/share/images/download_in_progress.png b/share/images/share_in_progress.png similarity index 100% rename from share/images/download_in_progress.png rename to share/images/share_in_progress.png diff --git a/share/images/download_in_progress_none.png b/share/images/share_in_progress_none.png similarity index 100% rename from share/images/download_in_progress_none.png rename to share/images/share_in_progress_none.png diff --git a/share/images/upload_window_gray.png b/share/images/upload_window_gray.png new file mode 100644 index 0000000000000000000000000000000000000000..80db4b8f3ea1c99a62abb91d46c37574e5025196 GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$0wn*`OvwRKOiAAEE)4(M`_JqL@;D1TB8wRq zxP?KOkzv*x37{Z*iKnkC`y*CX26G|q8~en8Lc2X(977~7hn_R^KAgbN^63*NN(72z;YDq^~Y?{>8a1}n4b+yV@msCD=RF}|}tqkgn+WyjaZh4ciur=E^o7HPh zS?PMrnRSDW`FGr%r-lbFL|pz~Ri5Lbabv^F3#}q^i^J`*o*a?Chc6!YYg-TgQu&X%Q~loCIHg9Y|H=v literal 0 HcmV?d00001 diff --git a/share/images/upload_window_green.png b/share/images/upload_window_green.png new file mode 100644 index 0000000000000000000000000000000000000000..652ddaffab94dc23d69039aaf2099bf00585d5fb GIT binary patch literal 483 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$3?vg*uer*=z{ncl6XF_nNPmQ*|NsBz#!j9G z3>U_dAirP+hi5m^K%69RcNc~ZR#^`qhqJ&VvY3H^TNs2H8D`Cq01C2~c>21sKVoHN zFqe6gFj)X76z=Kb7{YNq`NOAgAHRO)|G>?|#h17ty@A=Et(fIFpOd7da?2Jc=dFo3 zQv(zNS~o0BO>uU9`b2a=(4vJ4lL~DAEL_M`z*nB3!9VA)oYX1C=LSZGn;SA1X0!9? U^LuFs01boFyt=akR{0Du#)E&u=k literal 0 HcmV?d00001 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." } From a0db6d0ee77b4378cb5d5458f2b7a35e21cf4a34 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 4 May 2018 18:08:23 -0700 Subject: [PATCH 061/126] 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 """ From 23821ebae62097f6e8900f95a9eaefb9d1824be5 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 15:44:04 -0700 Subject: [PATCH 062/126] 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 From 4d5f1a34cd6f4ce77e0f358323356a8697a1c02c Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 16:21:22 -0700 Subject: [PATCH 063/126] 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 From 996f6c072579a8a1f81aa76dae11809b57fbbcc3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 16:38:29 -0700 Subject: [PATCH 064/126] 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", From 591e97a57a33e6b7364cd81a34b9148fdda816e1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 22:15:29 -0700 Subject: [PATCH 065/126] 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 From 9d557d4aa0dc75f5033cb980b4819bcfbae70a7f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 22:16:45 -0700 Subject: [PATCH 066/126] 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. """ From eb3d6f217164098762ff6cc2a7d4d42aad6c319a Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 7 May 2018 23:07:11 -0700 Subject: [PATCH 067/126] 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 {}" } From 841e47b2349a6d7aec717ed4f785a244dc6a5d13 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 8 May 2018 13:35:50 -0700 Subject: [PATCH 068/126] ReceiveModeRequest should only deal with upload_ids for upload requests, not for other requests --- onionshare/web.py | 70 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 33 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') + 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 - }) + # Update the GUI on the upload progress + self.web.add_request(Web.REQUEST_PROGRESS, self.path, { + 'id': self.upload_id, + 'progress': self.progress + }) From a787a5af1ec764676b1fc1bdc0c1e7b6e173efef Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Tue, 8 May 2018 14:28:02 -0700 Subject: [PATCH 069/126] 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 = {} From c23ab77a589a7b0b3f0f812e633a9537367ec331 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 20:51:01 -0700 Subject: [PATCH 070/126] 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): """ From db7d5a65522a61df980347e5a9f4d3bbf6fdc4a9 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 21:11:57 -0700 Subject: [PATCH 071/126] 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"])) From caf87b8d96bd1702e91021720c46d109e74bfdf3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 21:20:51 -0700 Subject: [PATCH 072/126] 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 From ee9c0d0abb326bc8e59e35212399e4adf135e27b Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 22:36:08 -0700 Subject: [PATCH 073/126] 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 | 150 ++++++++++++++---------- share/images/open_folder.png | Bin 0 -> 221 bytes share/locale/en.json | 2 +- 6 files changed, 120 insertions(+), 70 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() + # 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) - # 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) + # 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: - # 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__() - 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 + 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(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) + 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 0000000000000000000000000000000000000000..0a734c415e121640abd526e09aca4ab15a82db2c GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$0wn*`OvwRKOiAAEE)4(M`_JqL@;D1TB8wRq zxP?KOkzv*x37{Z*iKnkC`y*BnR#}NGj!XlfP@boYV~E7%+H<>k4+ZeFK0F`paYZnZ ztu0}q&xEA`yLu*YYG?@hG_Y7N;bmp|w4=Z;@#Xf~HwO|{demu@RLR83zS(DE=U9F< zBT@J!x5q1;^^=RY6?WFYH{-su&nt1p&KmzaYCjmYFWiW3*7f_qqGlsEJLJ8M4$x)> MPgg&ebxsLQ07pAWL;wH) literal 0 HcmV?d00001 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 {}" } From 7a571764ef2b8776dc36d32cfa7266fcf382d334 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 19 May 2018 22:58:55 -0700 Subject: [PATCH 074/126] 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 | 67 +++++++++++++++++-------- share/locale/en.json | 3 +- 6 files changed, 70 insertions(+), 25 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 {}" } From 8939d279e383cf2e344bf24b86b9c3dcf2be9007 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 11:04:45 -0700 Subject: [PATCH 075/126] 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() From 9857d9fce812f84c9a42d8cb84aed39348e05745 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 11:16:09 -0700 Subject: [PATCH 076/126] 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 %} From 18573ba49cd22a2b399fc2abe8fc7af2cdbaf116 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 12:07:15 -0700 Subject: [PATCH 077/126] 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. From d6ce902eb6d45fdba6b7adcc6a77b2d7f95924bc Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 13:13:06 -0700 Subject: [PATCH 078/126] 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 From 451e07269f377e5bb3f2d053b18279b6d977d380 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:05:34 -0700 Subject: [PATCH 079/126] 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) From b20ba6fc8647e7b49600b08ef14032783e2740bf Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:12:53 -0700 Subject: [PATCH 080/126] 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): """ From f5ce0690314f79ed22d9615226aab52ec71c2a1f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 14:40:27 -0700 Subject: [PATCH 081/126] 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: {}" } From 96a680e05d8ce71bd7593a0165ac0403122b62eb Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 15:20:21 -0700 Subject: [PATCH 082/126] 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", From 4fd93636daf1048339be8c6e42f707e716dff45d Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 20 May 2018 15:33:13 -0700 Subject: [PATCH 083/126] 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. From a4f0b5e8f8287a9e60ca59b472a60b28e7f43776 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 13 Jul 2018 15:50:17 +1000 Subject: [PATCH 084/126] 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() From 89e341c8ec2bdce41444207d32b5c22d4f6a6d73 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 14 Jul 2018 16:19:16 +1000 Subject: [PATCH 085/126] #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. From 69ae29272cf834a55607d00474dce5fca943d94e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 14 Jul 2018 16:43:21 +1000 Subject: [PATCH 086/126] 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): From 4092a65e0c7f145e3dc816e76931baac8863a904 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Jul 2018 11:45:14 +1000 Subject: [PATCH 087/126] 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 %} From e37dbb3efbe4974caf0a59d8ebaffda7c1d3fa62 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Jul 2018 11:53:55 +1000 Subject: [PATCH 088/126] 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 %} From 3b45f93dbeb1d22ad15553fc4f2e0308a910f5e7 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 21 Jul 2018 17:06:11 +1000 Subject: [PATCH 089/126] 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 @@
From a13e98d7bb36f0dc0ec56903914870611cb930d6 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 14:58:14 +1000 Subject: [PATCH 090/126] 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 From e4d1f8b8f67a138d6b250ed178576eac892984eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 16:20:19 +1000 Subject: [PATCH 091/126] #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" } From e343aa194e8e5f1df748b7174a67b62ef480a048 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 16:26:10 +1000 Subject: [PATCH 092/126] 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%" } From 5442beba8b5759f6ee2a243f594642942036fdc4 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:00:30 +1000 Subject: [PATCH 093/126] 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): From 1d8a902407d24e5cd51aa914be1b5d61d29f4e82 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:13:04 +1000 Subject: [PATCH 094/126] 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 From 1a273e772b951dcb54e470fa52e7e9e8a7d5c857 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 22 Jul 2018 17:13:23 +1000 Subject: [PATCH 095/126] 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%" } From 2de9359629d07a57cf7a439f485907a0cc0d2560 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 21 Aug 2018 19:31:02 +1000 Subject: [PATCH 096/126] 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: From 5c8b0d77965caba786677098bc01b4aab152e480 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:45:08 +1000 Subject: [PATCH 097/126] 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()) From 16430f5fad1df7747848154d9e3dbf8e6e4a5249 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 11:50:16 +1000 Subject: [PATCH 098/126] 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': '', From f0efb10d08a51c8176773ea7bb432cbaa36de3ec Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 22 Aug 2018 17:03:15 +1000 Subject: [PATCH 099/126] 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: ", From 47fc55aac1d200ca9624d60ac91af20e3d120216 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 23 Aug 2018 11:02:28 +1000 Subject: [PATCH 100/126] 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)", From 7879697ec6966deabf732b841afee9e0a0bf681f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 28 Aug 2018 09:33:49 +1000 Subject: [PATCH 101/126] 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() From 808c5a33331c393a9ce92c2d34ff008576866cd0 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 1 Sep 2018 09:20:50 +1000 Subject: [PATCH 102/126] 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 From f9e614eba125846dc8739fb8e09f04a6f432da36 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 11:35:28 +1000 Subject: [PATCH 103/126] 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 From 8955ce0699f391f1950d2029e4aeaf615245285f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:21:38 +1000 Subject: [PATCH 104/126] Refactor the onionkey stuff to be more like @maqp's revised version (thanks) --- onionshare/onionkey.py | 60 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 29 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 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, i): - return (h[i // 8] >> (i % 8)) & 1 + 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 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): +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. From 0b0eef724561eec094482c1c7961e40bc62fbb13 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 12:29:48 +1000 Subject: [PATCH 105/126] 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(): From 25eed81b004532481a6e794dca6698a6adc08920 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Thu, 13 Sep 2018 16:35:24 +1000 Subject: [PATCH 106/126] 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.", From e54a1473ce277ee6796b6a2ce23e3d09977372eb Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 15 Sep 2018 11:36:34 +1000 Subject: [PATCH 107/126] 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']) From 30ee2290d78837c1c8dfabdc31a9ddfb7098a386 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 15 Sep 2018 16:07:08 +1000 Subject: [PATCH 108/126] 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: From 52981b6f98d2a29ec1e382da670c03fb876bdcec Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:05:40 -0700 Subject: [PATCH 109/126] 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 From 9815c612ebff3f099997301c4964e5f163001d40 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:47:42 -0700 Subject: [PATCH 110/126] 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): """ From 73f09f14c4a1e05df5d7f3c5058129cfa0dd099f Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sat, 15 Sep 2018 19:52:53 -0700 Subject: [PATCH 111/126] 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.

From d8566c2d7868e003faef1228b76fc35940891304 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 13:54:47 +1000 Subject: [PATCH 112/126] 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)", From 95f097eae32ba2a46f9d483a6e8b5584f56b4ae5 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:00:41 +1000 Subject: [PATCH 113/126] 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) From 026322b4585cddd8c03e903484d07abf4248411b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:06:55 +1000 Subject: [PATCH 114/126] 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) From 6efa5f15b731a8eea25932504bc8c8e564f2a3bc Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 14:18:44 +1000 Subject: [PATCH 115/126] 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 From 7bd897d19ec0dab18519f6b3c48ebdf40138eaba Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sun, 16 Sep 2018 15:15:40 +1000 Subject: [PATCH 116/126] 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() From 4777c45ad8f7aa987e217f2b18b89f6937de3eae Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 16 Sep 2018 13:50:30 -0700 Subject: [PATCH 117/126] 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() From 8c3c0eb02bb608e35519d18faf83ffa2f24ea336 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:43:19 +1000 Subject: [PATCH 118/126] 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", From 7c55f0adaea6257fc3758f74390ec297114a4b5e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 08:44:56 +1000 Subject: [PATCH 119/126] 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) From 6c01d7a2daa360ad768932109bb023f2a76c9ced Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:01:30 +1000 Subject: [PATCH 120/126] 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", From ff8b7df5a55a37e3a486cbb1bf48bf75220c7c1b Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:02:35 +1000 Subject: [PATCH 121/126] 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: ", From 6ed5c94df75101fc9f62a10fe46323369ffa2dbf Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:12:13 +1000 Subject: [PATCH 122/126] 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 From adf4b0298035a3079f78db1b87d31bf356ead59f Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 09:16:11 +1000 Subject: [PATCH 123/126] 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 From 953727419c8f419940bf0fdad4db26956ba452ee Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Mon, 17 Sep 2018 18:48:22 +1000 Subject: [PATCH 124/126] 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: {}", From 359e4703833a66eaa9ca7f83ead8053346e9c1a3 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 16:11:52 -0700 Subject: [PATCH 125/126] 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", From 7c5d1545193c4a2a09cfee29c751f31b3cbcd1d1 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Mon, 17 Sep 2018 17:42:21 -0700 Subject: [PATCH 126/126] 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): """