mirror of
https://github.com/onionshare/onionshare.git
synced 2025-08-01 19:06:20 -04:00
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:
commit
d69bba4c9d
88 changed files with 5751 additions and 1668 deletions
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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())
|
||||
|
|
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.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
77
onionshare_gui/threads.py
Normal 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'))
|
Loading…
Add table
Add a link
Reference in a new issue