summaryrefslogtreecommitdiff
path: root/onionshare_gui
diff options
context:
space:
mode:
authorMiguel Jacq <mig@mig5.net>2018-10-01 16:42:54 +1000
committerMiguel Jacq <mig@mig5.net>2018-10-01 16:42:54 +1000
commitf653e8cc04f09427afb219a82dbf7cf33090d45a (patch)
tree62c5f1c0c88b0083e6c8d3bed3e29e026c660c19 /onionshare_gui
parent997e2f87ee003616388b96e65471500490a773ba (diff)
parent2ffcdbb1083dece7664792f7bef9dbf2245e549e (diff)
downloadonionshare-f653e8cc04f09427afb219a82dbf7cf33090d45a.tar.gz
onionshare-f653e8cc04f09427afb219a82dbf7cf33090d45a.zip
Merge develop in and fix upload/timer functionality so that it works as described. Still needs fixing to not throw a connection error to the lucky last uploader after their upload completes and server stops due to expiry having passed
Diffstat (limited to 'onionshare_gui')
-rw-r--r--onionshare_gui/__init__.py10
-rw-r--r--onionshare_gui/mode.py72
-rw-r--r--onionshare_gui/onion_thread.py45
-rw-r--r--onionshare_gui/onionshare_gui.py9
-rw-r--r--onionshare_gui/receive_mode/__init__.py42
-rw-r--r--onionshare_gui/receive_mode/uploads.py28
-rw-r--r--onionshare_gui/server_status.py26
-rw-r--r--onionshare_gui/settings_dialog.py2
-rw-r--r--onionshare_gui/share_mode/__init__.py79
-rw-r--r--onionshare_gui/share_mode/downloads.py18
-rw-r--r--onionshare_gui/share_mode/threads.py60
-rw-r--r--onionshare_gui/threads.py77
12 files changed, 284 insertions, 184 deletions
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py
index 13f0e8c7..99db635a 100644
--- a/onionshare_gui/__init__.py
+++ b/onionshare_gui/__init__.py
@@ -18,7 +18,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import division
-import os, sys, platform, argparse
+import os
+import sys
+import platform
+import argparse
+import signal
from .widgets import Alert
from PyQt5 import QtCore, QtWidgets
@@ -58,6 +62,10 @@ def main():
strings.load_strings(common)
print(strings._('version_string').format(common.version))
+ # Allow Ctrl-C to smoothly quit the program instead of throwing an exception
+ # https://stackoverflow.com/questions/42814093/how-to-handle-ctrlc-in-python-app-with-pyqt
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
# Start the Qt app
global qtapp
qtapp = Application(common)
diff --git a/onionshare_gui/mode.py b/onionshare_gui/mode.py
index 418afffd..6b156f7e 100644
--- a/onionshare_gui/mode.py
+++ b/onionshare_gui/mode.py
@@ -17,15 +17,13 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-import time
-import threading
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings
from onionshare.common import ShutdownTimer
from .server_status import ServerStatus
-from .onion_thread import OnionThread
+from .threads import OnionThread
from .widgets import Alert
class Mode(QtWidgets.QWidget):
@@ -39,7 +37,7 @@ class Mode(QtWidgets.QWidget):
starting_server_error = QtCore.pyqtSignal(str)
set_server_active = QtCore.pyqtSignal(bool)
- def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None):
+ def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False):
super(Mode, self).__init__()
self.common = common
self.qtapp = qtapp
@@ -56,8 +54,15 @@ class Mode(QtWidgets.QWidget):
# The web object gets created in init()
self.web = None
+ # Local mode is passed from OnionShareGui
+ self.local_only = local_only
+
+ # Threads start out as None
+ self.onion_thread = None
+ self.web_thread = None
+
# Server status
- self.server_status = ServerStatus(self.common, self.qtapp, self.app)
+ self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only)
self.server_status.server_started.connect(self.start_server)
self.server_status.server_stopped.connect(self.stop_server)
self.server_status.server_canceled.connect(self.cancel_server)
@@ -76,7 +81,15 @@ class Mode(QtWidgets.QWidget):
# Layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.primary_action)
- self.setLayout(self.layout)
+ # Hack to allow a minimum width on self.layout
+ min_width_widget = QtWidgets.QWidget()
+ min_width_widget.setMinimumWidth(450)
+ self.layout.addWidget(min_width_widget)
+
+ self.horizontal_layout_wrapper = QtWidgets.QHBoxLayout()
+ self.horizontal_layout_wrapper.addLayout(self.layout)
+
+ self.setLayout(self.horizontal_layout_wrapper)
def init(self):
"""
@@ -138,34 +151,11 @@ class Mode(QtWidgets.QWidget):
self.status_bar.clearMessage()
self.server_status_label.setText('')
- # Start the onion service in a new thread
- def start_onion_service(self):
- # Choose a port for the web app
- self.app.choose_port()
-
- # Start http service in new thread
- t = threading.Thread(target=self.web.start, args=(self.app.port, not self.common.settings.get('close_after_first_download'), self.common.settings.get('public_mode'), self.common.settings.get('slug')))
- t.daemon = True
- t.start()
-
- # Wait for the web app slug to generate before continuing
- if not self.common.settings.get('public_mode'):
- while self.web.slug == None:
- time.sleep(0.1)
-
- # Now start the onion service
- try:
- self.app.start_onion_service()
- self.starting_server_step2.emit()
-
- except Exception as e:
- self.starting_server_error.emit(e.args[0])
- return
-
self.common.log('Mode', 'start_server', 'Starting an onion thread')
- self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
- self.t.daemon = True
- self.t.start()
+ self.onion_thread = OnionThread(self)
+ self.onion_thread.success.connect(self.starting_server_step2.emit)
+ self.onion_thread.error.connect(self.starting_server_error.emit)
+ self.onion_thread.start()
def start_server_custom(self):
"""
@@ -243,10 +233,22 @@ class Mode(QtWidgets.QWidget):
"""
Cancel the server while it is preparing to start
"""
- if self.t:
- self.t.quit()
+ self.cancel_server_custom()
+
+ if self.onion_thread:
+ self.common.log('Mode', 'cancel_server: quitting onion thread')
+ self.onion_thread.quit()
+ if self.web_thread:
+ self.common.log('Mode', 'cancel_server: quitting web thread')
+ self.web_thread.quit()
self.stop_server()
+ def cancel_server_custom(self):
+ """
+ Add custom initialization here.
+ """
+ pass
+
def stop_server(self):
"""
Stop the onionshare server.
diff --git a/onionshare_gui/onion_thread.py b/onionshare_gui/onion_thread.py
deleted file mode 100644
index 0a25e891..00000000
--- a/onionshare_gui/onion_thread.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-OnionShare | https://onionshare.org/
-
-Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""
-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 b63119bb..83f3a7e0 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -55,7 +55,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
- self.setMinimumWidth(450)
+ self.setMinimumWidth(850)
# Load settings
self.config = config
@@ -121,7 +121,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.setStatusBar(self.status_bar)
# Share mode
- self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames)
+ self.share_mode = ShareMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, filenames, self.local_only)
self.share_mode.init()
self.share_mode.server_status.server_started.connect(self.update_server_status_indicator)
self.share_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
@@ -135,7 +135,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.share_mode.set_server_active.connect(self.set_server_active)
# Receive mode
- self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray)
+ self.receive_mode = ReceiveMode(self.common, qtapp, app, self.status_bar, self.server_status_label, self.system_tray, None, self.local_only)
self.receive_mode.init()
self.receive_mode.server_status.server_started.connect(self.update_server_status_indicator)
self.receive_mode.server_status.server_stopped.connect(self.update_server_status_indicator)
@@ -205,9 +205,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.update_server_status_indicator()
- # Wait 1ms for the event loop to finish, then adjust size
- QtCore.QTimer.singleShot(1, self.adjustSize)
-
def share_mode_clicked(self):
if self.mode != self.MODE_SHARE:
self.common.log('OnionShareGui', 'share_mode_clicked')
diff --git a/onionshare_gui/receive_mode/__init__.py b/onionshare_gui/receive_mode/__init__.py
index fd644d5b..faa65ffe 100644
--- a/onionshare_gui/receive_mode/__init__.py
+++ b/onionshare_gui/receive_mode/__init__.py
@@ -34,7 +34,7 @@ class ReceiveMode(Mode):
Custom initialization for ReceiveMode.
"""
# Create the Web object
- self.web = Web(self.common, True, True)
+ self.web = Web(self.common, True, 'receive')
# Server status
self.server_status.set_mode('receive')
@@ -46,19 +46,13 @@ class ReceiveMode(Mode):
self.server_status.web = self.web
self.server_status.update()
- # Downloads
+ # Uploads
self.uploads = Uploads(self.common)
self.uploads_in_progress = 0
self.uploads_completed = 0
self.new_upload = False # For scrolling to the bottom of the uploads list
# Information about share, and show uploads button
- self.info_show_uploads = QtWidgets.QToolButton()
- self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png')))
- self.info_show_uploads.setCheckable(True)
- self.info_show_uploads.toggled.connect(self.uploads_toggled)
- self.info_show_uploads.setToolTip(strings._('gui_uploads_window_tooltip', True))
-
self.info_in_progress_uploads_count = QtWidgets.QLabel()
self.info_in_progress_uploads_count.setStyleSheet(self.common.css['mode_info_label'])
@@ -72,7 +66,6 @@ class ReceiveMode(Mode):
self.info_layout.addStretch()
self.info_layout.addWidget(self.info_in_progress_uploads_count)
self.info_layout.addWidget(self.info_completed_uploads_count)
- self.info_layout.addWidget(self.info_show_uploads)
self.info_widget = QtWidgets.QWidget()
self.info_widget.setLayout(self.info_layout)
@@ -86,6 +79,8 @@ class ReceiveMode(Mode):
# Layout
self.layout.insertWidget(0, self.receive_info)
self.layout.insertWidget(0, self.info_widget)
+ self.layout.addStretch()
+ self.horizontal_layout_wrapper.addWidget(self.uploads)
def get_stop_server_shutdown_timeout_text(self):
"""
@@ -97,16 +92,15 @@ class ReceiveMode(Mode):
"""
The shutdown timer expired, should we stop the server? Returns a bool
"""
- # TODO: wait until the final upload is done before stoppign the server?
# If there were no attempts to upload files, or all uploads are done, we can stop
- if self.web.upload_count == 0 or not self.web.uploads_in_progress:
+ if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress:
self.server_status.stop_server()
self.server_status_label.setText(strings._('close_on_timeout', True))
return True
# An upload is probably still running - hold off on stopping the share, but block new shares.
else:
self.server_status_label.setText(strings._('timeout_upload_still_running', True))
- self.web.can_upload = False
+ self.web.receive_mode.can_upload = False
return False
return True
@@ -116,7 +110,7 @@ class ReceiveMode(Mode):
Starting the server.
"""
# Reset web counters
- self.web.upload_count = 0
+ self.web.receive_mode.upload_count = 0
self.web.error404_count = 0
# Hide and reset the uploads if we have previously shared
@@ -177,6 +171,12 @@ class ReceiveMode(Mode):
Handle REQUEST_UPLOAD_FINISHED event.
"""
self.uploads.finished(event["data"]["id"])
+ # Update the total 'completed uploads' info
+ self.uploads_completed += 1
+ self.update_uploads_completed()
+ # Update the 'in progress uploads' info
+ self.uploads_in_progress -= 1
+ self.update_uploads_in_progress()
def on_reload_settings(self):
"""
@@ -193,12 +193,11 @@ class ReceiveMode(Mode):
self.uploads_in_progress = 0
self.update_uploads_completed()
self.update_uploads_in_progress()
- self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_gray.png')))
self.uploads.reset()
def update_uploads_completed(self):
"""
- Update the 'Downloads completed' info widget.
+ Update the 'Uploads completed' info widget.
"""
if self.uploads_completed == 0:
image = self.common.get_resource_path('images/share_completed_none.png')
@@ -209,13 +208,12 @@ class ReceiveMode(Mode):
def update_uploads_in_progress(self):
"""
- Update the 'Downloads in progress' info widget.
+ Update the 'Uploads in progress' info widget.
"""
if self.uploads_in_progress == 0:
image = self.common.get_resource_path('images/share_in_progress_none.png')
else:
image = self.common.get_resource_path('images/share_in_progress.png')
- self.info_show_uploads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/upload_window_green.png')))
self.info_in_progress_uploads_count.setText('<img src="{0:s}" /> {1:d}'.format(image, self.uploads_in_progress))
self.info_in_progress_uploads_count.setToolTip(strings._('info_in_progress_uploads_tooltip', True).format(self.uploads_in_progress))
@@ -230,13 +228,3 @@ class ReceiveMode(Mode):
# Resize window
self.adjustSize()
-
- def uploads_toggled(self, checked):
- """
- When the 'Show/hide uploads' button is toggled, show or hide the uploads window.
- """
- self.common.log('ReceiveMode', 'toggle_uploads')
- if checked:
- self.uploads.show()
- else:
- self.uploads.hide()
diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py
index 09834156..48574cc7 100644
--- a/onionshare_gui/receive_mode/uploads.py
+++ b/onionshare_gui/receive_mode/uploads.py
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import subprocess
+import textwrap
from datetime import datetime
from PyQt5 import QtCore, QtWidgets, QtGui
@@ -221,16 +222,20 @@ class Uploads(QtWidgets.QScrollArea):
self.setWindowTitle(strings._('gui_uploads', True))
self.setWidgetResizable(True)
- self.setMaximumHeight(600)
self.setMinimumHeight(150)
self.setMinimumWidth(350)
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
self.vbar = self.verticalScrollBar()
+ self.vbar.rangeChanged.connect(self.resizeScroll)
uploads_label = QtWidgets.QLabel(strings._('gui_uploads', True))
uploads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads', True))
+ self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True))
+ self.clear_history_button.clicked.connect(self.reset)
+ self.clear_history_button.hide()
+
self.uploads_layout = QtWidgets.QVBoxLayout()
@@ -238,11 +243,18 @@ class Uploads(QtWidgets.QScrollArea):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(uploads_label)
layout.addWidget(self.no_uploads_label)
+ layout.addWidget(self.clear_history_button)
layout.addLayout(self.uploads_layout)
layout.addStretch()
widget.setLayout(layout)
self.setWidget(widget)
+ def resizeScroll(self, minimum, maximum):
+ """
+ Scroll to the bottom of the window when the range changes.
+ """
+ self.vbar.setValue(maximum)
+
def add(self, upload_id, content_length):
"""
Add a new upload.
@@ -250,15 +262,14 @@ class Uploads(QtWidgets.QScrollArea):
self.common.log('Uploads', 'add', 'upload_id: {}, content_length: {}'.format(upload_id, content_length))
# Hide the no_uploads_label
self.no_uploads_label.hide()
+ # Show the clear_history_button
+ self.clear_history_button.show()
# Add it to the list
upload = Upload(self.common, upload_id, content_length)
self.uploads[upload_id] = upload
self.uploads_layout.addWidget(upload)
- # Scroll to the bottom
- self.vbar.setValue(self.vbar.maximum())
-
def update(self, upload_id, progress):
"""
Update the progress of an upload.
@@ -290,10 +301,12 @@ class Uploads(QtWidgets.QScrollArea):
"""
self.common.log('Uploads', 'reset')
for upload in self.uploads.values():
+ upload.close()
self.uploads_layout.removeWidget(upload)
self.uploads = {}
self.no_uploads_label.show()
+ self.clear_history_button.hide()
self.resize(self.sizeHint())
def resizeEvent(self, event):
@@ -301,10 +314,7 @@ class Uploads(QtWidgets.QScrollArea):
try:
for upload in self.uploads.values():
for item in upload.files.values():
- if item.filename_label_width > width:
- item.filename_label.setText(item.filename[:25] + '[...]')
- item.adjustSize()
- if width > item.filename_label_width:
- item.filename_label.setText(item.filename)
+ item.filename_label.setText(textwrap.fill(item.filename, 30))
+ item.adjustSize()
except:
pass
diff --git a/onionshare_gui/server_status.py b/onionshare_gui/server_status.py
index 9afceb38..32135ca4 100644
--- a/onionshare_gui/server_status.py
+++ b/onionshare_gui/server_status.py
@@ -44,7 +44,7 @@ class ServerStatus(QtWidgets.QWidget):
STATUS_WORKING = 1
STATUS_STARTED = 2
- def __init__(self, common, qtapp, app, file_selection=None):
+ def __init__(self, common, qtapp, app, file_selection=None, local_only=False):
super(ServerStatus, self).__init__()
self.common = common
@@ -56,17 +56,23 @@ class ServerStatus(QtWidgets.QWidget):
self.app = app
self.web = None
+ self.local_only = local_only
self.resizeEvent(None)
# Shutdown timeout layout
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
self.shutdown_timeout = QtWidgets.QDateTimeEdit()
- # Set proposed timeout to be 5 minutes into the future
self.shutdown_timeout.setDisplayFormat("hh:mm A MMM d, yy")
- self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 2 min from now
- self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ if self.local_only:
+ # For testing
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(15))
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime())
+ else:
+ # Set proposed timeout to be 5 minutes into the future
+ self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
+ # Onion services can take a little while to start, so reduce the risk of it expiring too soon by setting the minimum to 60s from now
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
shutdown_timeout_layout = QtWidgets.QHBoxLayout()
shutdown_timeout_layout.addWidget(self.shutdown_timeout_label)
@@ -154,7 +160,8 @@ class ServerStatus(QtWidgets.QWidget):
Reset the timeout in the UI after stopping a share
"""
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
- self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
+ if not self.local_only:
+ self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(60))
def update(self):
"""
@@ -255,8 +262,11 @@ class ServerStatus(QtWidgets.QWidget):
"""
if self.status == self.STATUS_STOPPED:
if self.common.settings.get('shutdown_timeout'):
- # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
- self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
+ if self.local_only:
+ self.timeout = self.shutdown_timeout.dateTime().toPyDateTime()
+ else:
+ # Get the timeout chosen, stripped of its seconds. This prevents confusion if the share stops at (say) 37 seconds past the minute chosen
+ self.timeout = self.shutdown_timeout.dateTime().toPyDateTime().replace(second=0, microsecond=0)
# If the timeout has actually passed already before the user hit Start, refuse to start the server.
if QtCore.QDateTime.currentDateTime().toPyDateTime() > self.timeout:
Alert(self.common, strings._('gui_server_timeout_expired', QtWidgets.QMessageBox.Warning))
diff --git a/onionshare_gui/settings_dialog.py b/onionshare_gui/settings_dialog.py
index bf0f12ea..1cec527b 100644
--- a/onionshare_gui/settings_dialog.py
+++ b/onionshare_gui/settings_dialog.py
@@ -734,7 +734,7 @@ class SettingsDialog(QtWidgets.QDialog):
onion.connect(custom_settings=settings, config=self.config, tor_status_update_func=tor_status_update_func)
# If an exception hasn't been raised yet, the Tor settings work
- Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth))
+ Alert(self.common, strings._('settings_test_success', True).format(onion.tor_version, onion.supports_ephemeral, onion.supports_stealth, onion.supports_next_gen_onions))
# Clean up
onion.cleanup()
diff --git a/onionshare_gui/share_mode/__init__.py b/onionshare_gui/share_mode/__init__.py
index 37315bbe..90fce49a 100644
--- a/onionshare_gui/share_mode/__init__.py
+++ b/onionshare_gui/share_mode/__init__.py
@@ -17,7 +17,6 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-import threading
import os
from PyQt5 import QtCore, QtWidgets, QtGui
@@ -28,6 +27,7 @@ from onionshare.web import Web
from .file_selection import FileSelection
from .downloads import Downloads
+from .threads import CompressThread
from ..mode import Mode
from ..widgets import Alert
@@ -39,8 +39,11 @@ class ShareMode(Mode):
"""
Custom initialization for ReceiveMode.
"""
+ # Threads start out as None
+ self.compress_thread = None
+
# Create the Web object
- self.web = Web(self.common, True, False)
+ self.web = Web(self.common, True, 'share')
# File selection
self.file_selection = FileSelection(self.common)
@@ -76,12 +79,6 @@ class ShareMode(Mode):
self.info_label = QtWidgets.QLabel()
self.info_label.setStyleSheet(self.common.css['mode_info_label'])
- self.info_show_downloads = QtWidgets.QToolButton()
- self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
- self.info_show_downloads.setCheckable(True)
- self.info_show_downloads.toggled.connect(self.downloads_toggled)
- self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True))
-
self.info_in_progress_downloads_count = QtWidgets.QLabel()
self.info_in_progress_downloads_count.setStyleSheet(self.common.css['mode_info_label'])
@@ -96,7 +93,6 @@ class ShareMode(Mode):
self.info_layout.addStretch()
self.info_layout.addWidget(self.info_in_progress_downloads_count)
self.info_layout.addWidget(self.info_completed_downloads_count)
- self.info_layout.addWidget(self.info_show_downloads)
self.info_widget = QtWidgets.QWidget()
self.info_widget.setLayout(self.info_layout)
@@ -113,6 +109,7 @@ class ShareMode(Mode):
# Layout
self.layout.insertLayout(0, self.file_selection)
self.layout.insertWidget(0, self.info_widget)
+ self.horizontal_layout_wrapper.addWidget(self.downloads)
# Always start with focus on file selection
self.file_selection.setFocus()
@@ -128,7 +125,7 @@ class ShareMode(Mode):
The shutdown timer expired, should we stop the server? Returns a bool
"""
# If there were no attempts to download the share, or all downloads are done, we can stop
- if self.web.download_count == 0 or self.web.done:
+ if self.web.share_mode.download_count == 0 or self.web.done:
self.server_status.stop_server()
self.server_status_label.setText(strings._('close_on_timeout', True))
return True
@@ -142,7 +139,7 @@ class ShareMode(Mode):
Starting the server.
"""
# Reset web counters
- self.web.download_count = 0
+ self.web.share_mode.download_count = 0
self.web.error404_count = 0
# Hide and reset the downloads if we have previously shared
@@ -161,28 +158,13 @@ class ShareMode(Mode):
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
self.status_bar.insertWidget(0, self._zip_progress_bar)
- # Prepare the files for sending in a new thread
- def finish_starting_server(self):
- # Prepare files to share
- def _set_processed_size(x):
- if self._zip_progress_bar != None:
- self._zip_progress_bar.update_processed_size_signal.emit(x)
-
- try:
- self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
- self.app.cleanup_filenames.append(self.web.zip_filename)
-
- # Only continue if the server hasn't been canceled
- if self.server_status.status != self.server_status.STATUS_STOPPED:
- self.starting_server_step3.emit()
- self.start_server_finished.emit()
- except OSError as e:
- self.starting_server_error.emit(e.strerror)
- return
-
- t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
- t.daemon = True
- t.start()
+ # prepare the files for sending in a new thread
+ self.compress_thread = CompressThread(self)
+ self.compress_thread.success.connect(self.starting_server_step3.emit)
+ self.compress_thread.success.connect(self.start_server_finished.emit)
+ self.compress_thread.error.connect(self.starting_server_error.emit)
+ self.server_status.server_canceled.connect(self.compress_thread.cancel)
+ self.compress_thread.start()
def start_server_step3_custom(self):
"""
@@ -195,7 +177,7 @@ class ShareMode(Mode):
self._zip_progress_bar = None
# Warn about sending large files over Tor
- if self.web.zip_filesize >= 157286400: # 150mb
+ if self.web.share_mode.download_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show()
@@ -222,6 +204,14 @@ class ShareMode(Mode):
self.update_downloads_in_progress()
self.file_selection.file_list.adjustSize()
+ def cancel_server_custom(self):
+ """
+ Stop the compression thread on cancel
+ """
+ if self.compress_thread:
+ self.common.log('OnionShareGui', 'cancel_server: quitting compress thread')
+ self.compress_thread.quit()
+
def handle_tor_broke_custom(self):
"""
Connection to Tor broke.
@@ -239,7 +229,11 @@ class ShareMode(Mode):
"""
Handle REQUEST_STARTED event.
"""
- self.downloads.add(event["data"]["id"], self.web.zip_filesize)
+ if event["data"]["use_gzip"]:
+ filesize = self.web.share_mode.gzip_filesize
+ else:
+ filesize = self.web.share_mode.download_filesize
+ self.downloads.add(event["data"]["id"], filesize)
self.downloads_in_progress += 1
self.update_downloads_in_progress()
@@ -252,7 +246,7 @@ class ShareMode(Mode):
self.downloads.update(event["data"]["id"], event["data"]["bytes"])
# Is the download complete?
- if event["data"]["bytes"] == self.web.zip_filesize:
+ if event["data"]["bytes"] == self.web.share_mode.filesize:
self.system_tray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
# Update the total 'completed downloads' info
@@ -319,16 +313,6 @@ class ShareMode(Mode):
# Resize window
self.adjustSize()
- def downloads_toggled(self, checked):
- """
- When the 'Show/hide downloads' button is toggled, show or hide the downloads window.
- """
- self.common.log('ShareMode', 'toggle_downloads')
- if checked:
- self.downloads.show()
- else:
- self.downloads.hide()
-
def reset_info_counters(self):
"""
Set the info counters back to zero.
@@ -337,7 +321,6 @@ class ShareMode(Mode):
self.downloads_in_progress = 0
self.update_downloads_completed()
self.update_downloads_in_progress()
- self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
self.downloads.reset()
def update_downloads_completed(self):
@@ -359,7 +342,6 @@ class ShareMode(Mode):
image = self.common.get_resource_path('images/share_in_progress_none.png')
else:
image = self.common.get_resource_path('images/share_in_progress.png')
- self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png')))
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {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))
@@ -410,6 +392,7 @@ class ZipProgressBar(QtWidgets.QProgressBar):
def update_processed_size(self, val):
self._processed_size = val
+
if self.processed_size < self.total_files_size:
self.setValue(int((self.processed_size * 100) / self.total_files_size))
elif self.total_files_size != 0:
diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py
index f5e8512e..a34796f1 100644
--- a/onionshare_gui/share_mode/downloads.py
+++ b/onionshare_gui/share_mode/downloads.py
@@ -91,16 +91,19 @@ class Downloads(QtWidgets.QScrollArea):
self.setWindowTitle(strings._('gui_downloads', True))
self.setWidgetResizable(True)
- self.setMaximumHeight(600)
self.setMinimumHeight(150)
self.setMinimumWidth(350)
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint)
self.vbar = self.verticalScrollBar()
+ self.vbar.rangeChanged.connect(self.resizeScroll)
downloads_label = QtWidgets.QLabel(strings._('gui_downloads', True))
downloads_label.setStyleSheet(self.common.css['downloads_uploads_label'])
self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads', True))
+ self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history', True))
+ self.clear_history_button.clicked.connect(self.reset)
+ self.clear_history_button.hide()
self.downloads_layout = QtWidgets.QVBoxLayout()
@@ -108,26 +111,32 @@ class Downloads(QtWidgets.QScrollArea):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(downloads_label)
layout.addWidget(self.no_downloads_label)
+ layout.addWidget(self.clear_history_button)
layout.addLayout(self.downloads_layout)
layout.addStretch()
widget.setLayout(layout)
self.setWidget(widget)
+ def resizeScroll(self, minimum, maximum):
+ """
+ Scroll to the bottom of the window when the range changes.
+ """
+ self.vbar.setValue(maximum)
+
def add(self, download_id, total_bytes):
"""
Add a new download progress bar.
"""
# Hide the no_downloads_label
self.no_downloads_label.hide()
+ # Show the clear_history_button
+ self.clear_history_button.show()
# Add it to the list
download = Download(self.common, download_id, total_bytes)
self.downloads[download_id] = download
self.downloads_layout.addWidget(download.progress_bar)
- # Scroll to the bottom
- self.vbar.setValue(self.vbar.maximum())
-
def update(self, download_id, downloaded_bytes):
"""
Update the progress of a download progress bar.
@@ -150,4 +159,5 @@ class Downloads(QtWidgets.QScrollArea):
self.downloads = {}
self.no_downloads_label.show()
+ self.clear_history_button.hide()
self.resize(self.sizeHint())
diff --git a/onionshare_gui/share_mode/threads.py b/onionshare_gui/share_mode/threads.py
new file mode 100644
index 00000000..d6022746
--- /dev/null
+++ b/onionshare_gui/share_mode/threads.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+from PyQt5 import QtCore
+
+
+class CompressThread(QtCore.QThread):
+ """
+ Compresses files to be shared
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(CompressThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('CompressThread', '__init__')
+
+ # prepare files to share
+ def set_processed_size(self, x):
+ if self.mode._zip_progress_bar != None:
+ self.mode._zip_progress_bar.update_processed_size_signal.emit(x)
+
+ def run(self):
+ self.mode.common.log('CompressThread', 'run')
+
+ try:
+ if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size):
+ self.success.emit()
+ else:
+ # Cancelled
+ pass
+
+ self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames
+ except OSError as e:
+ self.error.emit(e.strerror)
+
+ def cancel(self):
+ self.mode.common.log('CompressThread', 'cancel')
+
+ # Let the Web and ZipWriter objects know that we're canceling compression early
+ self.mode.web.cancel_compression = True
+ if self.mode.web.zip_writer:
+ self.mode.web.zip_writer.cancel_compression = True
diff --git a/onionshare_gui/threads.py b/onionshare_gui/threads.py
new file mode 100644
index 00000000..3b05bebf
--- /dev/null
+++ b/onionshare_gui/threads.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+"""
+OnionShare | https://onionshare.org/
+
+Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+import time
+from PyQt5 import QtCore
+
+from onionshare.onion import *
+
+
+class OnionThread(QtCore.QThread):
+ """
+ Starts the onion service, and waits for it to finish
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(OnionThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('OnionThread', '__init__')
+
+ # allow this thread to be terminated
+ self.setTerminationEnabled()
+
+ def run(self):
+ self.mode.common.log('OnionThread', 'run')
+
+ self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
+
+ # start onionshare http service in new thread
+ self.mode.web_thread = WebThread(self.mode)
+ self.mode.web_thread.start()
+
+ # wait for modules in thread to load, preventing a thread-related cx_Freeze crash
+ time.sleep(0.2)
+
+ try:
+ self.mode.app.start_onion_service()
+ self.success.emit()
+
+ except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
+ self.error.emit(e.args[0])
+ return
+
+
+class WebThread(QtCore.QThread):
+ """
+ Starts the web service
+ """
+ success = QtCore.pyqtSignal()
+ error = QtCore.pyqtSignal(str)
+
+ def __init__(self, mode):
+ super(WebThread, self).__init__()
+ self.mode = mode
+ self.mode.common.log('WebThread', '__init__')
+
+ def run(self):
+ self.mode.common.log('WebThread', 'run')
+ self.mode.app.choose_port()
+ self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('public_mode'), self.mode.common.settings.get('slug'))