onionshare/onionshare_gui/onionshare_gui.py

899 lines
39 KiB
Python

# -*- coding: utf-8 -*-
"""
OnionShare | https://onionshare.org/
Copyright (C) 2017 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 os
import threading
import time
import queue
from PyQt5 import QtCore, QtWidgets, QtGui
from onionshare import strings, common
from onionshare.common import Common, ShutdownTimer
from onionshare.onion import *
from .tor_connection_dialog import TorConnectionDialog
from .settings_dialog import SettingsDialog
from .file_selection import FileSelection
from .server_status import ServerStatus
from .downloads import Downloads
from .alert import Alert
from .update_checker import UpdateThread
class OnionShareGui(QtWidgets.QMainWindow):
"""
OnionShareGui is the main window for the GUI that contains all of the
GUI elements.
"""
start_server_finished = QtCore.pyqtSignal()
stop_server_finished = QtCore.pyqtSignal()
starting_server_step2 = QtCore.pyqtSignal()
starting_server_step3 = QtCore.pyqtSignal()
starting_server_error = QtCore.pyqtSignal(str)
MODE_SHARE = 'share'
MODE_RECEIVE = 'receive'
def __init__(self, common, web, onion, qtapp, app, filenames, config=False, local_only=False):
super(OnionShareGui, self).__init__()
self.common = common
self.common.log('OnionShareGui', '__init__')
self._initSystemTray()
self.web = web
self.onion = onion
self.qtapp = qtapp
self.app = app
self.local_only = local_only
self.mode = self.MODE_SHARE
self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.setMinimumWidth(430)
# Load settings
self.config = config
self.common.load_settings(self.config)
# Mode switcher, to switch between share files and receive files
self.mode_switcher_selected_style = """
QPushButton {
color: #ffffff;
background-color: #4e064f;
border: 0;
border-right: 1px solid #69266b;
font-weight: bold;
border-radius: 0;
}"""
self.mode_switcher_unselected_style = """
QPushButton {
color: #ffffff;
background-color: #601f61;
border: 0;
font-weight: normal;
border-radius: 0;
}"""
self.share_mode_button = QtWidgets.QPushButton(strings._('gui_mode_share_button', True));
self.share_mode_button.setFixedHeight(50)
self.share_mode_button.clicked.connect(self.share_mode_clicked)
self.receive_mode_button = QtWidgets.QPushButton(strings._('gui_mode_receive_button', True));
self.receive_mode_button.setFixedHeight(50)
self.receive_mode_button.clicked.connect(self.receive_mode_clicked)
self.settings_button = QtWidgets.QPushButton()
self.settings_button.setDefault(False)
self.settings_button.setFixedWidth(40)
self.settings_button.setFixedHeight(50)
self.settings_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/settings.png')) )
self.settings_button.clicked.connect(self.open_settings)
self.settings_button.setStyleSheet("""
QPushButton {
background-color: #601f61;
border: 0;
border-left: 1px solid #69266b;
border-radius: 0;
}""")
mode_switcher_layout = QtWidgets.QHBoxLayout();
mode_switcher_layout.setSpacing(0)
mode_switcher_layout.addWidget(self.share_mode_button)
mode_switcher_layout.addWidget(self.receive_mode_button)
mode_switcher_layout.addWidget(self.settings_button)
self.update_mode_switcher()
# File selection
self.file_selection = FileSelection(self.common)
if filenames:
for filename in filenames:
self.file_selection.file_list.add_file(filename)
# Server status
self.server_status = ServerStatus(self.common, self.qtapp, self.app, self.web, self.file_selection)
self.server_status.server_started.connect(self.file_selection.server_started)
self.server_status.server_started.connect(self.start_server)
self.server_status.server_started.connect(self.update_server_status_indicator)
self.server_status.server_stopped.connect(self.file_selection.server_stopped)
self.server_status.server_stopped.connect(self.stop_server)
self.server_status.server_stopped.connect(self.update_server_status_indicator)
self.server_status.server_stopped.connect(self.update_primary_action)
self.server_status.server_canceled.connect(self.cancel_server)
self.server_status.server_canceled.connect(self.file_selection.server_stopped)
self.server_status.server_canceled.connect(self.update_primary_action)
self.start_server_finished.connect(self.clear_message)
self.start_server_finished.connect(self.server_status.start_server_finished)
self.start_server_finished.connect(self.update_server_status_indicator)
self.stop_server_finished.connect(self.server_status.stop_server_finished)
self.stop_server_finished.connect(self.update_server_status_indicator)
self.file_selection.file_list.files_updated.connect(self.server_status.update)
self.file_selection.file_list.files_updated.connect(self.update_primary_action)
self.server_status.url_copied.connect(self.copy_url)
self.server_status.hidservauth_copied.connect(self.copy_hidservauth)
self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3)
self.starting_server_error.connect(self.start_server_error)
self.server_status.button_clicked.connect(self.clear_message)
# Filesize warning
self.filesize_warning = QtWidgets.QLabel()
self.filesize_warning.setWordWrap(True)
self.filesize_warning.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.filesize_warning.hide()
# Downloads
self.downloads = Downloads(self.common)
self.new_download = False
self.downloads_in_progress = 0
self.downloads_completed = 0
# Info label along top of screen
self.info_layout = QtWidgets.QHBoxLayout()
self.info_label = QtWidgets.QLabel()
self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
self.info_show_downloads = QtWidgets.QToolButton()
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
self.info_show_downloads.setCheckable(True)
self.info_show_downloads.toggled.connect(self.downloads_toggled)
self.info_show_downloads.setToolTip(strings._('gui_downloads_window_tooltip', True))
self.info_in_progress_downloads_count = QtWidgets.QLabel()
self.info_in_progress_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
self.info_completed_downloads_count = QtWidgets.QLabel()
self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
self.update_downloads_completed(self.downloads_in_progress)
self.update_downloads_in_progress(self.downloads_in_progress)
self.info_layout.addWidget(self.info_label)
self.info_layout.addStretch()
self.info_layout.addWidget(self.info_in_progress_downloads_count)
self.info_layout.addWidget(self.info_completed_downloads_count)
self.info_layout.addWidget(self.info_show_downloads)
self.info_widget = QtWidgets.QWidget()
self.info_widget.setLayout(self.info_layout)
self.info_widget.hide()
# Server status indicator on the status bar
self.server_status_image_stopped = QtGui.QImage(self.common.get_resource_path('images/server_stopped.png'))
self.server_status_image_working = QtGui.QImage(self.common.get_resource_path('images/server_working.png'))
self.server_status_image_started = QtGui.QImage(self.common.get_resource_path('images/server_started.png'))
self.server_status_image_label = QtWidgets.QLabel()
self.server_status_image_label.setFixedWidth(20)
self.server_status_label = QtWidgets.QLabel()
self.server_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; }')
server_status_indicator_layout = QtWidgets.QHBoxLayout()
server_status_indicator_layout.addWidget(self.server_status_image_label)
server_status_indicator_layout.addWidget(self.server_status_label)
self.server_status_indicator = QtWidgets.QWidget()
self.server_status_indicator.setLayout(server_status_indicator_layout)
self.update_server_status_indicator()
# Status bar
self.status_bar = QtWidgets.QStatusBar()
self.status_bar.setSizeGripEnabled(False)
self.status_bar.setStyleSheet("""
QStatusBar {
font-style: italic;
color: #666666;
}
QStatusBar::item {
border: 0px;
}""")
self.status_bar.addPermanentWidget(self.server_status_indicator)
self.setStatusBar(self.status_bar)
# Status bar, zip progress bar
self._zip_progress_bar = None
# Status bar, sharing messages
self.server_share_status_label = QtWidgets.QLabel('')
self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }')
self.status_bar.insertWidget(0, self.server_share_status_label)
# Primary action layout
primary_action_layout = QtWidgets.QVBoxLayout()
primary_action_layout.addWidget(self.server_status)
primary_action_layout.addWidget(self.filesize_warning)
self.primary_action = QtWidgets.QWidget()
self.primary_action.setLayout(primary_action_layout)
self.primary_action.hide()
self.update_primary_action()
# Layouts
contents_layout = QtWidgets.QVBoxLayout()
contents_layout.setContentsMargins(10, 10, 10, 10)
contents_layout.addWidget(self.info_widget)
contents_layout.addLayout(self.file_selection)
contents_layout.addWidget(self.primary_action)
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(mode_switcher_layout)
layout.addLayout(contents_layout)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.show()
# Always start with focus on file selection
self.file_selection.setFocus()
# The server isn't active yet
self.set_server_active(False)
# Create the timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.check_for_requests)
# Start the "Connecting to Tor" dialog, which calls onion.connect()
tor_con = TorConnectionDialog(self.common, self.qtapp, self.onion)
tor_con.canceled.connect(self._tor_connection_canceled)
tor_con.open_settings.connect(self._tor_connection_open_settings)
if not self.local_only:
tor_con.start()
# Start the timer
self.timer.start(500)
# After connecting to Tor, check for updates
self.check_for_updates()
def update_mode_switcher(self):
# Based on the current mode, switch the mode switcher button styles,
# and show and hide widgets to switch modes
if self.mode == self.MODE_SHARE:
self.share_mode_button.setStyleSheet(self.mode_switcher_selected_style)
self.receive_mode_button.setStyleSheet(self.mode_switcher_unselected_style)
else:
self.share_mode_button.setStyleSheet(self.mode_switcher_unselected_style)
self.receive_mode_button.setStyleSheet(self.mode_switcher_selected_style)
def share_mode_clicked(self):
self.mode = self.MODE_SHARE
self.update_mode_switcher()
def receive_mode_clicked(self):
self.mode = self.MODE_RECEIVE
self.update_mode_switcher()
def update_primary_action(self):
# Show or hide primary action layout
file_count = self.file_selection.file_list.count()
if file_count > 0:
self.primary_action.show()
self.info_widget.show()
# Update the file count in the info label
total_size_bytes = 0
for index in range(self.file_selection.file_list.count()):
item = self.file_selection.file_list.item(index)
total_size_bytes += item.size_bytes
total_size_readable = self.common.human_readable_filesize(total_size_bytes)
if file_count > 1:
self.info_label.setText(strings._('gui_file_info', True).format(file_count, total_size_readable))
else:
self.info_label.setText(strings._('gui_file_info_single', True).format(file_count, total_size_readable))
else:
self.primary_action.hide()
self.info_widget.hide()
# Resize window
self.adjustSize()
def update_server_status_indicator(self):
self.common.log('OnionShareGui', 'update_server_status_indicator')
# Set the status image
if self.server_status.status == self.server_status.STATUS_STOPPED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_stopped))
self.server_status_label.setText(strings._('gui_status_indicator_stopped', True))
elif self.server_status.status == self.server_status.STATUS_WORKING:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_working))
self.server_status_label.setText(strings._('gui_status_indicator_working', True))
elif self.server_status.status == self.server_status.STATUS_STARTED:
self.server_status_image_label.setPixmap(QtGui.QPixmap.fromImage(self.server_status_image_started))
self.server_status_label.setText(strings._('gui_status_indicator_started', True))
def _initSystemTray(self):
menu = QtWidgets.QMenu()
self.settingsAction = menu.addAction(strings._('gui_settings_window_title', True))
self.settingsAction.triggered.connect(self.open_settings)
self.helpAction = menu.addAction(strings._('gui_settings_button_help', True))
self.helpAction.triggered.connect(SettingsDialog.help_clicked)
self.exitAction = menu.addAction(strings._('systray_menu_exit', True))
self.exitAction.triggered.connect(self.close)
self.systemTray = QtWidgets.QSystemTrayIcon(self)
# The convention is Mac systray icons are always grayscale
if self.common.platform == 'Darwin':
self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo_grayscale.png')))
else:
self.systemTray.setIcon(QtGui.QIcon(self.common.get_resource_path('images/logo.png')))
self.systemTray.setContextMenu(menu)
self.systemTray.show()
def _tor_connection_canceled(self):
"""
If the user cancels before Tor finishes connecting, ask if they want to
quit, or open settings.
"""
self.common.log('OnionShareGui', '_tor_connection_canceled')
def ask():
a = Alert(self.common, strings._('gui_tor_connection_ask', True), QtWidgets.QMessageBox.Question, buttons=QtWidgets.QMessageBox.NoButton, autostart=False)
settings_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_open_settings', True))
quit_button = QtWidgets.QPushButton(strings._('gui_tor_connection_ask_quit', True))
a.addButton(settings_button, QtWidgets.QMessageBox.AcceptRole)
a.addButton(quit_button, QtWidgets.QMessageBox.RejectRole)
a.setDefaultButton(settings_button)
a.exec_()
if a.clickedButton() == settings_button:
# Open settings
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Settings button clicked')
self.open_settings()
if a.clickedButton() == quit_button:
# Quit
self.common.log('OnionShareGui', '_tor_connection_canceled', 'Quit button clicked')
# Wait 1ms for the event loop to finish, then quit
QtCore.QTimer.singleShot(1, self.qtapp.quit)
# Wait 100ms before asking
QtCore.QTimer.singleShot(100, ask)
def _tor_connection_open_settings(self):
"""
The TorConnectionDialog wants to open the Settings dialog
"""
self.common.log('OnionShareGui', '_tor_connection_open_settings')
# Wait 1ms for the event loop to finish closing the TorConnectionDialog
QtCore.QTimer.singleShot(1, self.open_settings)
def open_settings(self):
"""
Open the SettingsDialog.
"""
self.common.log('OnionShareGui', 'open_settings')
def reload_settings():
self.common.log('OnionShareGui', 'open_settings', 'settings have changed, reloading')
self.common.settings.load()
# We might've stopped the main requests timer if a Tor connection failed.
# If we've reloaded settings, we probably succeeded in obtaining a new
# connection. If so, restart the timer.
if not self.local_only:
if self.onion.is_authenticated():
if not self.timer.isActive():
self.timer.start(500)
# If there were some files listed for sharing, we should be ok to
# re-enable the 'Start Sharing' button now.
if self.server_status.file_selection.get_num_files() > 0:
self.primary_action.show()
self.info_widget.show()
self.status_bar.clearMessage()
# If we switched off the shutdown timeout setting, ensure the widget is hidden.
if not self.common.settings.get('shutdown_timeout'):
self.server_status.shutdown_timeout_container.hide()
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
d.settings_saved.connect(reload_settings)
d.exec_()
# When settings close, refresh the server status UI
self.server_status.update()
def start_server(self):
"""
Start the onionshare server. This uses multiple threads to start the Tor onion
server and the web app.
"""
self.common.log('OnionShareGui', 'start_server')
self.set_server_active(True)
self.app.set_stealth(self.common.settings.get('use_stealth'))
# Hide and reset the downloads if we have previously shared
self.downloads.reset_downloads()
self.reset_info_counters()
self.status_bar.clearMessage()
self.server_share_status_label.setText('')
# Reset web counters
self.web.download_count = 0
self.web.error404_count = 0
# start the onion service in a new thread
def start_onion_service(self):
try:
self.app.start_onion_service()
self.starting_server_step2.emit()
except (TorTooOld, TorErrorInvalidSetting, TorErrorAutomatic, TorErrorSocketPort, TorErrorSocketFile, TorErrorMissingPassword, TorErrorUnreadableCookieFile, TorErrorAuthError, TorErrorProtocolError, BundledTorTimeout, OSError) as e:
self.starting_server_error.emit(e.args[0])
return
self.app.stay_open = not self.common.settings.get('close_after_first_download')
# start onionshare http service in new thread
t = threading.Thread(target=self.web.start, args=(self.app.port, self.app.stay_open, self.common.settings.get('slug')))
t.daemon = True
t.start()
# wait for modules in thread to load, preventing a thread-related cx_Freeze crash
time.sleep(0.2)
self.common.log('OnionshareGui', 'start_server', 'Starting an onion thread')
self.t = OnionThread(self.common, function=start_onion_service, kwargs={'self': self})
self.t.daemon = True
self.t.start()
def start_server_step2(self):
"""
Step 2 in starting the onionshare server. Zipping up files.
"""
self.common.log('OnionShareGui', 'start_server_step2')
# add progress bar to the status bar, indicating the compressing of files.
self._zip_progress_bar = ZipProgressBar(0)
self.filenames = []
for index in range(self.file_selection.file_list.count()):
self.filenames.append(self.file_selection.file_list.item(index).filename)
self._zip_progress_bar.total_files_size = OnionShareGui._compute_total_size(self.filenames)
self.status_bar.insertWidget(0, self._zip_progress_bar)
# prepare the files for sending in a new thread
def finish_starting_server(self):
# prepare files to share
def _set_processed_size(x):
if self._zip_progress_bar != None:
self._zip_progress_bar.update_processed_size_signal.emit(x)
try:
self.web.set_file_info(self.filenames, processed_size_callback=_set_processed_size)
self.app.cleanup_filenames.append(self.web.zip_filename)
self.starting_server_step3.emit()
# done
self.start_server_finished.emit()
except OSError as e:
self.starting_server_error.emit(e.strerror)
return
t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True
t.start()
def start_server_step3(self):
"""
Step 3 in starting the onionshare server. This displays the large filesize
warning, if applicable.
"""
self.common.log('OnionShareGui', 'start_server_step3')
# Remove zip progress bar
if self._zip_progress_bar is not None:
self.status_bar.removeWidget(self._zip_progress_bar)
self._zip_progress_bar = None
# warn about sending large files over Tor
if self.web.zip_filesize >= 157286400: # 150mb
self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show()
if self.common.settings.get('shutdown_timeout'):
# Convert the date value to seconds between now and then
now = QtCore.QDateTime.currentDateTime()
self.timeout = now.secsTo(self.server_status.timeout)
# Set the shutdown timeout value
if self.timeout > 0:
self.app.shutdown_timer = ShutdownTimer(self.common, self.timeout)
self.app.shutdown_timer.start()
# The timeout has actually already passed since the user clicked Start. Probably the Onion service took too long to start.
else:
self.stop_server()
self.start_server_error(strings._('gui_server_started_after_timeout'))
def start_server_error(self, error):
"""
If there's an error when trying to start the onion service
"""
self.common.log('OnionShareGui', 'start_server_error')
self.set_server_active(False)
Alert(self.common, error, QtWidgets.QMessageBox.Warning)
self.server_status.stop_server()
if self._zip_progress_bar is not None:
self.status_bar.removeWidget(self._zip_progress_bar)
self._zip_progress_bar = None
self.status_bar.clearMessage()
def cancel_server(self):
"""
Cancel the server while it is preparing to start
"""
if self.t:
self.t.quit()
self.stop_server()
def stop_server(self):
"""
Stop the onionshare server.
"""
self.common.log('OnionShareGui', 'stop_server')
if self.server_status.status != self.server_status.STATUS_STOPPED:
try:
self.web.stop(self.app.port)
except:
# Probably we had no port to begin with (Onion service didn't start)
pass
self.app.cleanup()
# Remove ephemeral service, but don't disconnect from Tor
self.onion.cleanup(stop_tor=False)
self.filesize_warning.hide()
self.downloads_in_progress = 0
self.downloads_completed = 0
self.update_downloads_in_progress(0)
self.file_selection.file_list.adjustSize()
self.set_server_active(False)
self.stop_server_finished.emit()
def check_for_updates(self):
"""
Check for updates in a new thread, if enabled.
"""
if self.common.platform == 'Windows' or self.common.platform == 'Darwin':
if self.common.settings.get('use_autoupdate'):
def update_available(update_url, installed_version, latest_version):
Alert(self.common, strings._("update_available", True).format(update_url, installed_version, latest_version))
self.update_thread = UpdateThread(self.common, self.onion, self.config)
self.update_thread.update_available.connect(update_available)
self.update_thread.start()
@staticmethod
def _compute_total_size(filenames):
total_size = 0
for filename in filenames:
if os.path.isfile(filename):
total_size += os.path.getsize(filename)
if os.path.isdir(filename):
total_size += Common.dir_size(filename)
return total_size
def check_for_requests(self):
"""
Check for messages communicated from the web app, and update the GUI accordingly.
"""
self.update()
if not self.local_only:
# Have we lost connection to Tor somehow?
if not self.onion.is_authenticated():
self.timer.stop()
if self.server_status.status != self.server_status.STATUS_STOPPED:
self.server_status.stop_server()
self.primary_action.hide()
self.info_widget.hide()
self.status_bar.showMessage(strings._('gui_tor_connection_lost', True))
if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_tor_connection_lost', True), strings._('gui_tor_connection_error_settings', True))
# scroll to the bottom of the dl progress bar log pane
# if a new download has been added
if self.new_download:
self.downloads.downloads_container.vbar.setValue(self.downloads.downloads_container.vbar.maximum())
self.new_download = False
events = []
done = False
while not done:
try:
r = self.web.q.get(False)
events.append(r)
except queue.Empty:
done = True
for event in events:
if event["type"] == self.web.REQUEST_LOAD:
self.status_bar.showMessage(strings._('download_page_loaded', True))
elif event["type"] == self.web.REQUEST_DOWNLOAD:
self.downloads.no_downloads_label.hide()
self.downloads.add_download(event["data"]["id"], web.zip_filesize)
self.new_download = True
self.downloads_in_progress += 1
self.update_downloads_in_progress(self.downloads_in_progress)
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
elif event["type"] == self.web.REQUEST_RATE_LIMIT:
self.stop_server()
Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical)
elif event["type"] == self.web.REQUEST_PROGRESS:
self.downloads.update_download(event["data"]["id"], event["data"]["bytes"])
# is the download complete?
if event["data"]["bytes"] == self.web.zip_filesize:
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_completed_title', True), strings._('systray_download_completed_message', True))
# Update the total 'completed downloads' info
self.downloads_completed += 1
self.update_downloads_completed(self.downloads_completed)
# Update the 'in progress downloads' info
self.downloads_in_progress -= 1
self.update_downloads_in_progress(self.downloads_in_progress)
# close on finish?
if not self.web.stay_open:
self.server_status.stop_server()
self.status_bar.clearMessage()
self.server_share_status_label.setText(strings._('closing_automatically', True))
else:
if self.server_status.status == self.server_status.STATUS_STOPPED:
self.downloads.cancel_download(event["data"]["id"])
self.downloads_in_progress = 0
self.update_downloads_in_progress(self.downloads_in_progress)
elif event["type"] == self.web.REQUEST_CANCELED:
self.downloads.cancel_download(event["data"]["id"])
# Update the 'in progress downloads' info
self.downloads_in_progress -= 1
self.update_downloads_in_progress(self.downloads_in_progress)
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
elif event["path"] != '/favicon.ico':
self.status_bar.showMessage('[#{0:d}] {1:s}: {2:s}'.format(self.web.error404_count, strings._('other_page_loaded', True), event["path"]))
# If the auto-shutdown timer has stopped, stop the server
if self.server_status.status == self.server_status.STATUS_STARTED:
if self.app.shutdown_timer and self.common.settings.get('shutdown_timeout'):
if self.timeout > 0:
now = QtCore.QDateTime.currentDateTime()
seconds_remaining = now.secsTo(self.server_status.timeout)
self.server_status.server_button.setText(strings._('gui_stop_server_shutdown_timeout', True).format(seconds_remaining))
if not self.app.shutdown_timer.is_alive():
# If there were no attempts to download the share, or all downloads are done, we can stop
if self.web.download_count == 0 or self.web.done:
self.server_status.stop_server()
self.status_bar.clearMessage()
self.server_share_status_label.setText(strings._('close_on_timeout', True))
# A download is probably still running - hold off on stopping the share
else:
self.status_bar.clearMessage()
self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
def downloads_toggled(self, checked):
"""
When the 'Show/hide downloads' button is toggled, show or hide the downloads window.
"""
self.common.log('OnionShareGui', 'toggle_downloads')
if checked:
self.downloads.downloads_container.show()
else:
self.downloads.downloads_container.hide()
def copy_url(self):
"""
When the URL gets copied to the clipboard, display this in the status bar.
"""
self.common.log('OnionShareGui', 'copy_url')
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
def copy_hidservauth(self):
"""
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
"""
self.common.log('OnionShareGui', 'copy_hidservauth')
if self.systemTray.supportsMessages() and self.common.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
def clear_message(self):
"""
Clear messages from the status bar.
"""
self.status_bar.clearMessage()
def set_server_active(self, active):
"""
Disable the Settings button while an OnionShare server is active.
"""
if active:
self.settings_button.hide()
else:
self.settings_button.show()
# Disable settings menu action when server is active
self.settingsAction.setEnabled(not active)
def reset_info_counters(self):
"""
Set the info counters back to zero.
"""
self.update_downloads_completed(0)
self.update_downloads_in_progress(0)
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_gray.png')))
self.downloads.no_downloads_label.show()
self.downloads.downloads_container.resize(self.downloads.downloads_container.sizeHint())
def update_downloads_completed(self, count):
"""
Update the 'Downloads completed' info widget.
"""
if count == 0:
self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed_none.png')
else:
self.info_completed_downloads_image = self.common.get_resource_path('images/download_completed.png')
self.info_completed_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_completed_downloads_image, count))
self.info_completed_downloads_count.setToolTip(strings._('info_completed_downloads_tooltip', True).format(count))
def update_downloads_in_progress(self, count):
"""
Update the 'Downloads in progress' info widget.
"""
if count == 0:
self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress_none.png')
else:
self.info_in_progress_downloads_image = self.common.get_resource_path('images/download_in_progress.png')
self.info_show_downloads.setIcon(QtGui.QIcon(self.common.get_resource_path('images/download_window_green.png')))
self.info_in_progress_downloads_count.setText('<img src="{0:s}" /> {1:d}'.format(self.info_in_progress_downloads_image, count))
self.info_in_progress_downloads_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
def closeEvent(self, e):
self.common.log('OnionShareGui', 'closeEvent')
try:
if self.server_status.status != self.server_status.STATUS_STOPPED:
self.common.log('OnionShareGui', 'closeEvent, opening warning dialog')
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle(strings._('gui_quit_title', True))
dialog.setText(strings._('gui_quit_warning', True))
dialog.setIcon(QtWidgets.QMessageBox.Critical)
quit_button = dialog.addButton(strings._('gui_quit_warning_quit', True), QtWidgets.QMessageBox.YesRole)
dont_quit_button = dialog.addButton(strings._('gui_quit_warning_dont_quit', True), QtWidgets.QMessageBox.NoRole)
dialog.setDefaultButton(dont_quit_button)
reply = dialog.exec_()
# Quit
if reply == 0:
self.stop_server()
e.accept()
# Don't Quit
else:
e.ignore()
except:
e.accept()
class ZipProgressBar(QtWidgets.QProgressBar):
update_processed_size_signal = QtCore.pyqtSignal(int)
def __init__(self, total_files_size):
super(ZipProgressBar, self).__init__()
self.setMaximumHeight(20)
self.setMinimumWidth(200)
self.setValue(0)
self.setFormat(strings._('zip_progress_bar_format'))
cssStyleData ="""
QProgressBar {
border: 1px solid #4e064f;
background-color: #ffffff !important;
text-align: center;
color: #9b9b9b;
}
QProgressBar::chunk {
border: 0px;
background-color: #4e064f;
width: 10px;
}"""
self.setStyleSheet(cssStyleData)
self._total_files_size = total_files_size
self._processed_size = 0
self.update_processed_size_signal.connect(self.update_processed_size)
@property
def total_files_size(self):
return self._total_files_size
@total_files_size.setter
def total_files_size(self, val):
self._total_files_size = val
@property
def processed_size(self):
return self._processed_size
@processed_size.setter
def processed_size(self, val):
self.update_processed_size(val)
def update_processed_size(self, val):
self._processed_size = val
if self.processed_size < self.total_files_size:
self.setValue(int((self.processed_size * 100) / self.total_files_size))
elif self.total_files_size != 0:
self.setValue(100)
else:
self.setValue(0)
class OnionThread(QtCore.QThread):
"""
A QThread for starting our Onion Service.
By using QThread rather than threading.Thread, we are able
to call quit() or terminate() on the startup if the user
decided to cancel (in which case do not proceed with obtaining
the Onion address and starting the web server).
"""
def __init__(self, common, function, kwargs=None):
super(OnionThread, self).__init__()
self.common = common
self.common.log('OnionThread', '__init__')
self.function = function
if not kwargs:
self.kwargs = {}
else:
self.kwargs = kwargs
def run(self):
self.common.log('OnionThread', 'run')
self.function(**self.kwargs)