mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Refactor all of the threading.Threads into QThreads, and quit them all when canceling the server. When canceling the compression thread, specifically mass a cancel message into the Web and ZipWriter objects to make the bail out on compression early
This commit is contained in:
parent
06f90b91ce
commit
174de57405
@ -104,6 +104,8 @@ class Web(object):
|
|||||||
self.file_info = []
|
self.file_info = []
|
||||||
self.zip_filename = None
|
self.zip_filename = None
|
||||||
self.zip_filesize = None
|
self.zip_filesize = None
|
||||||
|
self.zip_writer = None
|
||||||
|
self.cancel_compression = False
|
||||||
|
|
||||||
self.security_headers = [
|
self.security_headers = [
|
||||||
('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
|
('Content-Security-Policy', 'default-src \'self\'; style-src \'self\'; script-src \'self\'; img-src \'self\' data:;'),
|
||||||
@ -534,14 +536,20 @@ class Web(object):
|
|||||||
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
self.file_info['files'] = sorted(self.file_info['files'], key=lambda k: k['basename'])
|
||||||
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
self.file_info['dirs'] = sorted(self.file_info['dirs'], key=lambda k: k['basename'])
|
||||||
|
|
||||||
# zip up the files and folders
|
# Zip up the files and folders
|
||||||
z = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
self.zip_writer = ZipWriter(self.common, processed_size_callback=processed_size_callback)
|
||||||
|
self.zip_filename = self.zip_writer.zip_filename
|
||||||
for info in self.file_info['files']:
|
for info in self.file_info['files']:
|
||||||
z.add_file(info['filename'])
|
self.zip_writer.add_file(info['filename'])
|
||||||
|
# Canceling early?
|
||||||
|
if self.cancel_compression:
|
||||||
|
self.zip_writer.close()
|
||||||
|
return
|
||||||
|
|
||||||
for info in self.file_info['dirs']:
|
for info in self.file_info['dirs']:
|
||||||
z.add_dir(info['filename'])
|
self.zip_writer.add_dir(info['filename'])
|
||||||
z.close()
|
|
||||||
self.zip_filename = z.zip_filename
|
self.zip_writer.close()
|
||||||
self.zip_filesize = os.path.getsize(self.zip_filename)
|
self.zip_filesize = os.path.getsize(self.zip_filename)
|
||||||
|
|
||||||
def _safe_select_jinja_autoescape(self, filename):
|
def _safe_select_jinja_autoescape(self, filename):
|
||||||
@ -653,6 +661,7 @@ class ZipWriter(object):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
def __init__(self, common, zip_filename=None, processed_size_callback=None):
|
||||||
self.common = common
|
self.common = common
|
||||||
|
self.cancel_compression = False
|
||||||
|
|
||||||
if zip_filename:
|
if zip_filename:
|
||||||
self.zip_filename = zip_filename
|
self.zip_filename = zip_filename
|
||||||
@ -681,6 +690,10 @@ class ZipWriter(object):
|
|||||||
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
dir_to_strip = os.path.dirname(filename.rstrip('/'))+'/'
|
||||||
for dirpath, dirnames, filenames in os.walk(filename):
|
for dirpath, dirnames, filenames in os.walk(filename):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
|
# Canceling early?
|
||||||
|
if self.cancel_compression:
|
||||||
|
return
|
||||||
|
|
||||||
full_filename = os.path.join(dirpath, f)
|
full_filename = os.path.join(dirpath, f)
|
||||||
if not os.path.islink(full_filename):
|
if not os.path.islink(full_filename):
|
||||||
arc_filename = full_filename[len(dir_to_strip):]
|
arc_filename = full_filename[len(dir_to_strip):]
|
||||||
|
@ -17,15 +17,13 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from onionshare import strings
|
from onionshare import strings
|
||||||
from onionshare.common import ShutdownTimer
|
from onionshare.common import ShutdownTimer
|
||||||
|
|
||||||
from .server_status import ServerStatus
|
from .server_status import ServerStatus
|
||||||
from .onion_thread import OnionThread
|
from .threads import OnionThread
|
||||||
from .widgets import Alert
|
from .widgets import Alert
|
||||||
|
|
||||||
class Mode(QtWidgets.QWidget):
|
class Mode(QtWidgets.QWidget):
|
||||||
@ -56,6 +54,10 @@ class Mode(QtWidgets.QWidget):
|
|||||||
# The web object gets created in init()
|
# The web object gets created in init()
|
||||||
self.web = None
|
self.web = None
|
||||||
|
|
||||||
|
# Threads start out as None
|
||||||
|
self.onion_thread = None
|
||||||
|
self.web_thread = None
|
||||||
|
|
||||||
# Server status
|
# Server status
|
||||||
self.server_status = ServerStatus(self.common, self.qtapp, self.app)
|
self.server_status = ServerStatus(self.common, self.qtapp, self.app)
|
||||||
self.server_status.server_started.connect(self.start_server)
|
self.server_status.server_started.connect(self.start_server)
|
||||||
@ -138,34 +140,11 @@ class Mode(QtWidgets.QWidget):
|
|||||||
self.status_bar.clearMessage()
|
self.status_bar.clearMessage()
|
||||||
self.server_status_label.setText('')
|
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.common.log('Mode', 'start_server', 'Starting an onion thread')
|
||||||
self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
|
self.onion_thread = OnionThread(self)
|
||||||
self.t.daemon = True
|
self.onion_thread.success.connect(self.starting_server_step2.emit)
|
||||||
self.t.start()
|
self.onion_thread.error.connect(self.starting_server_error.emit)
|
||||||
|
self.onion_thread.start()
|
||||||
|
|
||||||
def start_server_custom(self):
|
def start_server_custom(self):
|
||||||
"""
|
"""
|
||||||
@ -243,10 +222,22 @@ class Mode(QtWidgets.QWidget):
|
|||||||
"""
|
"""
|
||||||
Cancel the server while it is preparing to start
|
Cancel the server while it is preparing to start
|
||||||
"""
|
"""
|
||||||
if self.t:
|
self.cancel_server_custom()
|
||||||
self.t.quit()
|
|
||||||
|
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()
|
self.stop_server()
|
||||||
|
|
||||||
|
def cancel_server_custom(self):
|
||||||
|
"""
|
||||||
|
Add custom initialization here.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def stop_server(self):
|
def stop_server(self):
|
||||||
"""
|
"""
|
||||||
Stop the onionshare server.
|
Stop the onionshare server.
|
||||||
|
@ -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)
|
|
@ -17,7 +17,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
import threading
|
|
||||||
import os
|
import os
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
@ -28,6 +27,7 @@ from onionshare.web import Web
|
|||||||
|
|
||||||
from .file_selection import FileSelection
|
from .file_selection import FileSelection
|
||||||
from .downloads import Downloads
|
from .downloads import Downloads
|
||||||
|
from .threads import CompressThread
|
||||||
from ..mode import Mode
|
from ..mode import Mode
|
||||||
from ..widgets import Alert
|
from ..widgets import Alert
|
||||||
|
|
||||||
@ -39,6 +39,9 @@ class ShareMode(Mode):
|
|||||||
"""
|
"""
|
||||||
Custom initialization for ReceiveMode.
|
Custom initialization for ReceiveMode.
|
||||||
"""
|
"""
|
||||||
|
# Threads start out as None
|
||||||
|
self.compress_thread = None
|
||||||
|
|
||||||
# Create the Web object
|
# Create the Web object
|
||||||
self.web = Web(self.common, True, False)
|
self.web = Web(self.common, True, False)
|
||||||
|
|
||||||
@ -161,28 +164,13 @@ class ShareMode(Mode):
|
|||||||
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
|
self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames)
|
||||||
self.status_bar.insertWidget(0, self._zip_progress_bar)
|
self.status_bar.insertWidget(0, self._zip_progress_bar)
|
||||||
|
|
||||||
# Prepare the files for sending in a new thread
|
# prepare the files for sending in a new thread
|
||||||
def finish_starting_server(self):
|
self.compress_thread = CompressThread(self)
|
||||||
# Prepare files to share
|
self.compress_thread.success.connect(self.starting_server_step3.emit)
|
||||||
def _set_processed_size(x):
|
self.compress_thread.success.connect(self.start_server_finished.emit)
|
||||||
if self._zip_progress_bar != None:
|
self.compress_thread.error.connect(self.starting_server_error.emit)
|
||||||
self._zip_progress_bar.update_processed_size_signal.emit(x)
|
self.server_status.server_canceled.connect(self.compress_thread.cancel)
|
||||||
|
self.compress_thread.start()
|
||||||
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):
|
def start_server_step3_custom(self):
|
||||||
"""
|
"""
|
||||||
@ -222,6 +210,15 @@ class ShareMode(Mode):
|
|||||||
self.update_downloads_in_progress()
|
self.update_downloads_in_progress()
|
||||||
self.file_selection.file_list.adjustSize()
|
self.file_selection.file_list.adjustSize()
|
||||||
|
|
||||||
|
def cancel_server_custom(self):
|
||||||
|
"""
|
||||||
|
Stop the compression thread on cancel
|
||||||
|
"""
|
||||||
|
if self.compress_thread:
|
||||||
|
self.common.log('OnionShareGui', 'cancel_server: quitting compress thread')
|
||||||
|
self.compress_thread.cancel()
|
||||||
|
self.compress_thread.quit()
|
||||||
|
|
||||||
def handle_tor_broke_custom(self):
|
def handle_tor_broke_custom(self):
|
||||||
"""
|
"""
|
||||||
Connection to Tor broke.
|
Connection to Tor broke.
|
||||||
|
60
onionshare_gui/share_mode/threads.py
Normal file
60
onionshare_gui/share_mode/threads.py
Normal 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.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size):
|
||||||
|
self.success.emit()
|
||||||
|
else:
|
||||||
|
# Cancelled
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.mode.app.cleanup_filenames.append(self.mode.web.zip_filename)
|
||||||
|
except OSError as e:
|
||||||
|
self.error.emit(e.strerror)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.mode.common.log('CompressThread', 'cancel')
|
||||||
|
|
||||||
|
# Let the Web and ZipWriter objects know that we're canceling compression early
|
||||||
|
self.mode.web.cancel_compression = True
|
||||||
|
if self.mode.web.zip_writer:
|
||||||
|
self.mode.web.zip_writer.cancel_compression = True
|
74
onionshare_gui/threads.py
Normal file
74
onionshare_gui/threads.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# -*- 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
|
||||||
|
|
||||||
|
|
||||||
|
class OnionThread(QtCore.QThread):
|
||||||
|
"""
|
||||||
|
Starts the onion service, and waits for it to finish
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, mode):
|
||||||
|
super(OnionThread, self).__init__()
|
||||||
|
self.mode = mode
|
||||||
|
self.mode.common.log('OnionThread', '__init__')
|
||||||
|
|
||||||
|
# allow this thread to be terminated
|
||||||
|
self.setTerminationEnabled()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.mode.common.log('OnionThread', 'run')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.mode.app.start_onion_service()
|
||||||
|
self.success.emit()
|
||||||
|
|
||||||
|
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
|
||||||
|
self.error.emit(e.args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.mode.app.stay_open = not self.mode.common.settings.get('close_after_first_download')
|
||||||
|
|
||||||
|
# start onionshare http service in new thread
|
||||||
|
self.mode.web_thread = WebThread(self.mode)
|
||||||
|
self.mode.web_thread.start()
|
||||||
|
|
||||||
|
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
|
||||||
|
class WebThread(QtCore.QThread):
|
||||||
|
"""
|
||||||
|
Starts the web service
|
||||||
|
"""
|
||||||
|
success = QtCore.pyqtSignal()
|
||||||
|
error = QtCore.pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, mode):
|
||||||
|
super(WebThread, self).__init__()
|
||||||
|
self.mode = mode
|
||||||
|
self.mode.common.log('WebThread', '__init__')
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.mode.common.log('WebThread', 'run')
|
||||||
|
self.mode.web.start(self.mode.app.port, self.mode.app.stay_open, self.mode.common.settings.get('slug'))
|
Loading…
Reference in New Issue
Block a user