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

This commit is contained in:
Miguel Jacq 2018-10-01 16:42:54 +10:00
commit d69bba4c9d
88 changed files with 5751 additions and 1668 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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')

View file

@ -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()

View file

@ -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

View file

@ -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))

View file

@ -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()

View file

@ -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:

View file

@ -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())

View file

@ -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

77
onionshare_gui/threads.py Normal file
View file

@ -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'))