merge ux-update in, and fix conflict

This commit is contained in:
Miguel Jacq 2018-02-20 07:47:21 +11:00
commit b7df7f8dc9
No known key found for this signature in database
GPG Key ID: EEA4341C6D97A0B6
16 changed files with 278 additions and 164 deletions

View File

@ -201,7 +201,9 @@ Section "install"
File "${BINPATH}\share\html\index.html" File "${BINPATH}\share\html\index.html"
SetOutPath "$INSTDIR\share\images" SetOutPath "$INSTDIR\share\images"
File "${BINPATH}\share\images\favicon.ico"
File "${BINPATH}\share\images\file_delete.png" File "${BINPATH}\share\images\file_delete.png"
File "${BINPATH}\share\images\info.png"
File "${BINPATH}\share\images\logo.png" File "${BINPATH}\share\images\logo.png"
File "${BINPATH}\share\images\logo_transparent.png" File "${BINPATH}\share\images\logo_transparent.png"
File "${BINPATH}\share\images\logo_grayscale.png" File "${BINPATH}\share\images\logo_grayscale.png"
@ -209,7 +211,8 @@ Section "install"
File "${BINPATH}\share\images\server_stopped.png" File "${BINPATH}\share\images\server_stopped.png"
File "${BINPATH}\share\images\server_working.png" File "${BINPATH}\share\images\server_working.png"
File "${BINPATH}\share\images\settings.png" File "${BINPATH}\share\images\settings.png"
File "${BINPATH}\share\images\settings_inactive.png" File "${BINPATH}\share\images\web_file.png"
File "${BINPATH}\share\images\web_folder.png"
SetOutPath "$INSTDIR\share\locale" SetOutPath "$INSTDIR\share\locale"
File "${BINPATH}\share\locale\cs.json" File "${BINPATH}\share\locale\cs.json"
@ -380,7 +383,9 @@ FunctionEnd
Delete "$INSTDIR\share\html\404.html" Delete "$INSTDIR\share\html\404.html"
Delete "$INSTDIR\share\html\denied.html" Delete "$INSTDIR\share\html\denied.html"
Delete "$INSTDIR\share\html\index.html" Delete "$INSTDIR\share\html\index.html"
Delete "$INSTDIR\share\images\favicon.ico"
Delete "$INSTDIR\share\images\file_delete.png" Delete "$INSTDIR\share\images\file_delete.png"
Delete "$INSTDIR\share\images\info.png"
Delete "$INSTDIR\share\images\logo.png" Delete "$INSTDIR\share\images\logo.png"
Delete "$INSTDIR\share\images\logo_transparent.png" Delete "$INSTDIR\share\images\logo_transparent.png"
Delete "$INSTDIR\share\images\logo_grayscale.png" Delete "$INSTDIR\share\images\logo_grayscale.png"
@ -388,7 +393,8 @@ FunctionEnd
Delete "$INSTDIR\share\images\server_stopped.png" Delete "$INSTDIR\share\images\server_stopped.png"
Delete "$INSTDIR\share\images\server_working.png" Delete "$INSTDIR\share\images\server_working.png"
Delete "$INSTDIR\share\images\settings.png" Delete "$INSTDIR\share\images\settings.png"
Delete "$INSTDIR\share\images\settings_inactive.png" Delete "$INSTDIR\share\images\web_file.png"
Delete "$INSTDIR\share\images\web_folder.png"
Delete "$INSTDIR\share\license.txt" Delete "$INSTDIR\share\license.txt"
Delete "$INSTDIR\share\locale\cs.json" Delete "$INSTDIR\share\locale\cs.json"
Delete "$INSTDIR\share\locale\de.json" Delete "$INSTDIR\share\locale\de.json"

View File

@ -26,6 +26,7 @@ import queue
import socket import socket
import sys import sys
import tempfile import tempfile
import base64
from distutils.version import LooseVersion as Version from distutils.version import LooseVersion as Version
from urllib.request import urlopen from urllib.request import urlopen
@ -125,6 +126,12 @@ def add_request(request_type, path, data=None):
}) })
# Load and base64 encode images to pass into templates
favicon_b64 = base64.b64encode(open(common.get_resource_path('images/favicon.ico'), 'rb').read()).decode()
logo_b64 = base64.b64encode(open(common.get_resource_path('images/logo.png'), 'rb').read()).decode()
folder_b64 = base64.b64encode(open(common.get_resource_path('images/web_folder.png'), 'rb').read()).decode()
file_b64 = base64.b64encode(open(common.get_resource_path('images/web_file.png'), 'rb').read()).decode()
slug = None slug = None
@ -206,7 +213,10 @@ def index(slug_candidate):
global stay_open, download_in_progress global stay_open, download_in_progress
deny_download = not stay_open and download_in_progress deny_download = not stay_open and download_in_progress
if deny_download: if deny_download:
r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) r = make_response(render_template_string(
open(common.get_resource_path('html/denied.html')).read(),
favicon_b64=favicon_b64
))
for header, value in security_headers: for header, value in security_headers:
r.headers.set(header, value) r.headers.set(header, value)
return r return r
@ -215,6 +225,10 @@ def index(slug_candidate):
r = make_response(render_template_string( r = make_response(render_template_string(
open(common.get_resource_path('html/index.html')).read(), open(common.get_resource_path('html/index.html')).read(),
favicon_b64=favicon_b64,
logo_b64=logo_b64,
folder_b64=folder_b64,
file_b64=file_b64,
slug=slug, slug=slug,
file_info=file_info, file_info=file_info,
filename=os.path.basename(zip_filename), filename=os.path.basename(zip_filename),
@ -243,7 +257,10 @@ def download(slug_candidate):
global stay_open, download_in_progress, done global stay_open, download_in_progress, done
deny_download = not stay_open and download_in_progress deny_download = not stay_open and download_in_progress
if deny_download: if deny_download:
r = make_response(render_template_string(open(common.get_resource_path('html/denied.html')).read())) r = make_response(render_template_string(
open(common.get_resource_path('html/denied.html')).read(),
favicon_b64=favicon_b64
))
for header,value in security_headers: for header,value in security_headers:
r.headers.set(header, value) r.headers.set(header, value)
return r return r
@ -355,7 +372,10 @@ def page_not_found(e):
force_shutdown() force_shutdown()
print(strings._('error_rate_limit')) print(strings._('error_rate_limit'))
r = make_response(render_template_string(open(common.get_resource_path('html/404.html')).read()), 404) r = make_response(render_template_string(
open(common.get_resource_path('html/404.html')).read(),
favicon_b64=favicon_b64
), 404)
for header, value in security_headers: for header, value in security_headers:
r.headers.set(header, value) r.headers.set(header, value)
return r return r

View File

@ -145,7 +145,8 @@ class FileList(QtWidgets.QListWidget):
count = len(event.mimeData().urls()) count = len(event.mimeData().urls())
self.drop_count.setText('+{}'.format(count)) self.drop_count.setText('+{}'.format(count))
self.drop_count.setGeometry(self.width() - 60, self.height() - 40, 50, 30) size_hint = self.drop_count.sizeHint()
self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height())
self.drop_count.show() self.drop_count.show()
event.accept() event.accept()
else: else:
@ -207,15 +208,24 @@ class FileList(QtWidgets.QListWidget):
icon = ip.icon(fileinfo) icon = ip.icon(fileinfo)
if os.path.isfile(filename): if os.path.isfile(filename):
size = common.human_readable_filesize(fileinfo.size()) size_bytes = fileinfo.size()
size_readable = common.human_readable_filesize(size_bytes)
else: else:
size = common.human_readable_filesize(common.dir_size(filename)) size_bytes = common.dir_size(filename)
item_name = '{0:s} ({1:s})'.format(basename, size) size_readable = common.human_readable_filesize(size_bytes)
# Create a new item # Create a new item
item = QtWidgets.QListWidgetItem(item_name) item = QtWidgets.QListWidgetItem()
item.setToolTip(size)
item.setIcon(icon) item.setIcon(icon)
item.size_bytes = size_bytes
# Item's name and size labels
item_name = QtWidgets.QLabel(basename)
item_name.setWordWrap(False)
item_name.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
item_name.setStyleSheet('QLabel { color: #000000; font-size: 13px; }')
item_size = QtWidgets.QLabel(size_readable)
item_size.setStyleSheet('QLabel { color: #666666; font-size: 11px; }')
# Item's delete button # Item's delete button
def delete_item(): def delete_item():
@ -229,16 +239,22 @@ class FileList(QtWidgets.QListWidget):
item.item_button.setFlat(True) item.item_button.setFlat(True)
item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) ) item.item_button.setIcon( QtGui.QIcon(common.get_resource_path('images/file_delete.png')) )
item.item_button.clicked.connect(delete_item) item.item_button.clicked.connect(delete_item)
item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
# Create an item widget to display on the item # Create the item's widget and layouts
item_widget_layout = QtWidgets.QHBoxLayout() item_vlayout = QtWidgets.QVBoxLayout()
item_widget_layout.addStretch() item_vlayout.addWidget(item_name)
item_widget_layout.addWidget(item.item_button) item_vlayout.addWidget(item_size)
item_widget = QtWidgets.QWidget() item_hlayout = QtWidgets.QHBoxLayout()
item_widget.setLayout(item_widget_layout) item_hlayout.addLayout(item_vlayout)
item_hlayout.addWidget(item.item_button)
widget = QtWidgets.QWidget()
widget.setLayout(item_hlayout)
item.setSizeHint(widget.sizeHint())
self.addItem(item) self.addItem(item)
self.setItemWidget(item, item_widget) self.setItemWidget(item, widget)
self.files_updated.emit() self.files_updated.emit()
@ -252,12 +268,17 @@ class FileSelection(QtWidgets.QVBoxLayout):
super(FileSelection, self).__init__() super(FileSelection, self).__init__()
self.server_on = False self.server_on = False
# file list # Info label
self.info_label = QtWidgets.QLabel()
self.info_label.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
# File list
self.file_list = FileList() self.file_list = FileList()
self.file_list.currentItemChanged.connect(self.update) self.file_list.currentItemChanged.connect(self.update)
self.file_list.files_dropped.connect(self.update) self.file_list.files_dropped.connect(self.update)
self.file_list.files_updated.connect(self.update)
# buttons # Buttons
self.add_button = QtWidgets.QPushButton(strings._('gui_add', True)) self.add_button = QtWidgets.QPushButton(strings._('gui_add', True))
self.add_button.clicked.connect(self.add) self.add_button.clicked.connect(self.add)
self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True)) self.delete_button = QtWidgets.QPushButton(strings._('gui_delete', True))
@ -267,7 +288,8 @@ class FileSelection(QtWidgets.QVBoxLayout):
button_layout.addWidget(self.add_button) button_layout.addWidget(self.add_button)
button_layout.addWidget(self.delete_button) button_layout.addWidget(self.delete_button)
# add the widgets # Add the widgets
self.addWidget(self.info_label)
self.addWidget(self.file_list) self.addWidget(self.file_list)
self.addLayout(button_layout) self.addLayout(button_layout)
@ -277,6 +299,23 @@ class FileSelection(QtWidgets.QVBoxLayout):
""" """
Update the GUI elements based on the current state. Update the GUI elements based on the current state.
""" """
# Update the info label
file_count = self.file_list.count()
if file_count == 0:
self.info_label.hide()
else:
total_size_bytes = 0
for index in range(self.file_list.count()):
item = self.file_list.item(index)
total_size_bytes += item.size_bytes
total_size_readable = 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))
self.info_label.show()
# All buttons should be hidden if the server is on # All buttons should be hidden if the server is on
if self.server_on: if self.server_on:
self.add_button.hide() self.add_button.hide()

View File

@ -56,7 +56,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.setWindowTitle('OnionShare') self.setWindowTitle('OnionShare')
self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png')))
self.setMinimumWidth(350) self.setMinimumWidth(430)
# Load settings # Load settings
self.config = config self.config = config
@ -91,9 +91,11 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.starting_server_step2.connect(self.start_server_step2) self.starting_server_step2.connect(self.start_server_step2)
self.starting_server_step3.connect(self.start_server_step3) self.starting_server_step3.connect(self.start_server_step3)
self.starting_server_error.connect(self.start_server_error) self.starting_server_error.connect(self.start_server_error)
self.server_status.button_clicked.connect(self.clear_message)
# Filesize warning # Filesize warning
self.filesize_warning = QtWidgets.QLabel() 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.setStyleSheet('padding: 10px 0; font-weight: bold; color: #333333;')
self.filesize_warning.hide() self.filesize_warning.hide()
@ -133,24 +135,32 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Status bar # Status bar
self.status_bar = QtWidgets.QStatusBar() self.status_bar = QtWidgets.QStatusBar()
self.status_bar.setSizeGripEnabled(False) self.status_bar.setSizeGripEnabled(False)
self.status_bar.setStyleSheet("QStatusBar::item { border: 0px; }") statusBar_cssStyleData ="""
QStatusBar {
font-style: italic;
color: #666666;
}
QStatusBar::item {
border: 0px;
}"""
self.status_bar.setStyleSheet(statusBar_cssStyleData)
self.status_bar.addPermanentWidget(self.server_status_indicator) self.status_bar.addPermanentWidget(self.server_status_indicator)
self.status_bar.addPermanentWidget(self.settings_button) self.status_bar.addPermanentWidget(self.settings_button)
self.setStatusBar(self.status_bar) self.setStatusBar(self.status_bar)
# Status bar, zip progress bar # Status bar, zip progress bar
self._zip_progress_bar = None self._zip_progress_bar = None
# Status bar, sharing messages
# Persistent URL notification self.server_share_status_label = QtWidgets.QLabel('')
self.persistent_url_label = QtWidgets.QLabel(strings._('persistent_url_in_use', True)) self.server_share_status_label.setStyleSheet('QLabel { font-style: italic; color: #666666; padding: 2px; }')
self.persistent_url_label.setStyleSheet('font-weight: bold; color: #333333;') self.status_bar.insertWidget(0, self.server_share_status_label)
self.persistent_url_label.hide()
# Primary action layout # Primary action layout
primary_action_layout = QtWidgets.QVBoxLayout() primary_action_layout = QtWidgets.QVBoxLayout()
primary_action_layout.addWidget(self.server_status) primary_action_layout.addWidget(self.server_status)
primary_action_layout.addWidget(self.filesize_warning) primary_action_layout.addWidget(self.filesize_warning)
primary_action_layout.addWidget(self.persistent_url_label)
primary_action_layout.addWidget(self.downloads_container) primary_action_layout.addWidget(self.downloads_container)
self.primary_action = QtWidgets.QWidget() self.primary_action = QtWidgets.QWidget()
self.primary_action.setLayout(primary_action_layout) self.primary_action.setLayout(primary_action_layout)
@ -189,16 +199,15 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.check_for_updates() self.check_for_updates()
def update_primary_action(self): def update_primary_action(self):
common.log('OnionShareGui', 'update_primary_action')
# Resize window
self.adjustSize()
# Show or hide primary action layout # Show or hide primary action layout
if self.file_selection.file_list.count() > 0: if self.file_selection.file_list.count() > 0:
self.primary_action.show() self.primary_action.show()
else: else:
self.primary_action.hide() self.primary_action.hide()
# Resize window
self.adjustSize()
def update_server_status_indicator(self): def update_server_status_indicator(self):
common.log('OnionShareGui', 'update_server_status_indicator') common.log('OnionShareGui', 'update_server_status_indicator')
@ -316,6 +325,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.downloads_container.hide() self.downloads_container.hide()
self.downloads.reset_downloads() self.downloads.reset_downloads()
self.status_bar.clearMessage() self.status_bar.clearMessage()
self.server_share_status_label.setText('')
# Reset web counters # Reset web counters
web.download_count = 0 web.download_count = 0
@ -376,7 +386,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.starting_server_error.emit(e.strerror) self.starting_server_error.emit(e.strerror)
return return
#self.status_bar.showMessage(strings._('gui_starting_server2', True))
t = threading.Thread(target=finish_starting_server, kwargs={'self': self}) t = threading.Thread(target=finish_starting_server, kwargs={'self': self})
t.daemon = True t.daemon = True
t.start() t.start()
@ -398,7 +407,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.filesize_warning.setText(strings._("large_filesize", True)) self.filesize_warning.setText(strings._("large_filesize", True))
self.filesize_warning.show() self.filesize_warning.show()
if self.server_status.timer_enabled: if self.settings.get('shutdown_timeout'):
# Convert the date value to seconds between now and then # Convert the date value to seconds between now and then
now = QtCore.QDateTime.currentDateTime() now = QtCore.QDateTime.currentDateTime()
self.timeout = now.secsTo(self.server_status.timeout) self.timeout = now.secsTo(self.server_status.timeout)
@ -411,9 +420,6 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.stop_server() self.stop_server()
self.start_server_error(strings._('gui_server_started_after_timeout')) self.start_server_error(strings._('gui_server_started_after_timeout'))
if self.settings.get('save_private_key'):
self.persistent_url_label.show()
def start_server_error(self, error): def start_server_error(self, error):
""" """
If there's an error when trying to start the onion service If there's an error when trying to start the onion service
@ -453,10 +459,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Remove ephemeral service, but don't disconnect from Tor # Remove ephemeral service, but don't disconnect from Tor
self.onion.cleanup(stop_tor=False) self.onion.cleanup(stop_tor=False)
self.filesize_warning.hide() self.filesize_warning.hide()
self.persistent_url_label.hide() self.file_selection.file_list.adjustSize()
self.stop_server_finished.emit()
self.set_server_active(False) self.set_server_active(False)
self.stop_server_finished.emit()
def check_for_updates(self): def check_for_updates(self):
""" """
@ -539,7 +545,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
# close on finish? # close on finish?
if not web.get_stay_open(): if not web.get_stay_open():
self.server_status.stop_server() self.server_status.stop_server()
self.status_bar.showMessage(strings._('closing_automatically', True)) self.status_bar.clearMessage()
self.server_share_status_label.setText(strings._('closing_automatically', True))
else: else:
if self.server_status.status == self.server_status.STATUS_STOPPED: if self.server_status.status == self.server_status.STATUS_STOPPED:
self.downloads.cancel_download(event["data"]["id"]) self.downloads.cancel_download(event["data"]["id"])
@ -554,30 +561,34 @@ class OnionShareGui(QtWidgets.QMainWindow):
# If the auto-shutdown timer has stopped, stop the server # If the auto-shutdown timer has stopped, stop the server
if self.server_status.status == self.server_status.STATUS_STARTED: if self.server_status.status == self.server_status.STATUS_STARTED:
if self.app.shutdown_timer and self.server_status.timer_enabled: if self.app.shutdown_timer and self.settings.get('shutdown_timeout'):
if self.timeout > 0: if self.timeout > 0:
if not self.app.shutdown_timer.is_alive(): 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 there were no attempts to download the share, or all downloads are done, we can stop
if web.download_count == 0 or web.done: if web.download_count == 0 or web.done:
self.server_status.stop_server() self.server_status.stop_server()
self.status_bar.showMessage(strings._('close_on_timeout', True)) 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 # A download is probably still running - hold off on stopping the share
else: else:
self.status_bar.showMessage(strings._('timeout_download_still_running', True)) self.status_bar.clearMessage()
self.server_share_status_label.setText(strings._('timeout_download_still_running', True))
def copy_url(self): def copy_url(self):
""" """
When the URL gets copied to the clipboard, display this in the status bar. When the URL gets copied to the clipboard, display this in the status bar.
""" """
common.log('OnionShareGui', 'copy_url') common.log('OnionShareGui', 'copy_url')
self.status_bar.showMessage(strings._('gui_copied_url', True), 2000) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_url_title', True), strings._('gui_copied_url', True))
def copy_hidservauth(self): def copy_hidservauth(self):
""" """
When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar. When the stealth onion service HidServAuth gets copied to the clipboard, display this in the status bar.
""" """
common.log('OnionShareGui', 'copy_hidservauth') common.log('OnionShareGui', 'copy_hidservauth')
self.status_bar.showMessage(strings._('gui_copied_hidservauth', True), 2000) if self.systemTray.supportsMessages() and self.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('gui_copied_hidservauth_title', True), strings._('gui_copied_hidservauth', True))
def clear_message(self): def clear_message(self):
""" """
@ -589,11 +600,10 @@ class OnionShareGui(QtWidgets.QMainWindow):
""" """
Disable the Settings button while an OnionShare server is active. Disable the Settings button while an OnionShare server is active.
""" """
self.settings_button.setEnabled(not active)
if active: if active:
self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings_inactive.png')) ) self.settings_button.hide()
else: else:
self.settings_button.setIcon( QtGui.QIcon(common.get_resource_path('images/settings.png')) ) self.settings_button.show()
# Disable settings menu action when server is active # Disable settings menu action when server is active
self.settingsAction.setEnabled(not active) self.settingsAction.setEnabled(not active)

View File

@ -30,6 +30,7 @@ class ServerStatus(QtWidgets.QWidget):
server_started = QtCore.pyqtSignal() server_started = QtCore.pyqtSignal()
server_stopped = QtCore.pyqtSignal() server_stopped = QtCore.pyqtSignal()
server_canceled = QtCore.pyqtSignal() server_canceled = QtCore.pyqtSignal()
button_clicked = QtCore.pyqtSignal()
url_copied = QtCore.pyqtSignal() url_copied = QtCore.pyqtSignal()
hidservauth_copied = QtCore.pyqtSignal() hidservauth_copied = QtCore.pyqtSignal()
@ -48,17 +49,15 @@ class ServerStatus(QtWidgets.QWidget):
self.settings = settings self.settings = settings
# Helper boolean as this is used in a few places
self.timer_enabled = False
# Shutdown timeout layout # Shutdown timeout layout
self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True)) self.shutdown_timeout_label = QtWidgets.QLabel(strings._('gui_settings_shutdown_timeout', True))
self.shutdown_timeout = QtWidgets.QDateTimeEdit() self.shutdown_timeout = QtWidgets.QDateTimeEdit()
# Set proposed timeout to be 5 minutes into the future # 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)) 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 # 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)) self.shutdown_timeout.setMinimumDateTime(QtCore.QDateTime.currentDateTime().addSecs(120))
self.shutdown_timeout.setCurrentSectionIndex(4) self.shutdown_timeout.setCurrentSection(QtWidgets.QDateTimeEdit.MinuteSection)
shutdown_timeout_layout = QtWidgets.QHBoxLayout() shutdown_timeout_layout = QtWidgets.QHBoxLayout()
shutdown_timeout_layout.addWidget(self.shutdown_timeout_label) shutdown_timeout_layout.addWidget(self.shutdown_timeout_label)
shutdown_timeout_layout.addWidget(self.shutdown_timeout) shutdown_timeout_layout.addWidget(self.shutdown_timeout)
@ -84,6 +83,7 @@ class ServerStatus(QtWidgets.QWidget):
self.url.setFont(url_font) self.url.setFont(url_font)
self.url.setWordWrap(True) self.url.setWordWrap(True)
self.url.setMinimumHeight(60) self.url.setMinimumHeight(60)
self.url.setMinimumSize(self.url.sizeHint())
self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }') self.url.setStyleSheet('QLabel { background-color: #ffffff; color: #000000; padding: 10px; border: 1px solid #666666; }')
url_buttons_style = 'QPushButton { color: #3f7fcf; }' url_buttons_style = 'QPushButton { color: #3f7fcf; }'
@ -115,22 +115,6 @@ class ServerStatus(QtWidgets.QWidget):
self.update() self.update()
def shutdown_timeout_toggled(self, checked):
"""
Shutdown timer option was toggled. If checked, show the timer settings.
"""
if checked:
self.timer_enabled = True
# Hide the checkbox, show the options
self.shutdown_timeout_label.show()
# Reset the default timer to 5 minutes into the future after toggling the option on
self.shutdown_timeout.setDateTime(QtCore.QDateTime.currentDateTime().addSecs(300))
self.shutdown_timeout.show()
else:
self.timer_enabled = False
self.shutdown_timeout_label.hide()
self.shutdown_timeout.hide()
def shutdown_timeout_reset(self): def shutdown_timeout_reset(self):
""" """
Reset the timeout in the UI after stopping a share Reset the timeout in the UI after stopping a share
@ -146,10 +130,19 @@ class ServerStatus(QtWidgets.QWidget):
if self.status == self.STATUS_STARTED: if self.status == self.STATUS_STARTED:
self.url_description.show() self.url_description.show()
if self.settings.get('close_after_first_download'): info_image = common.get_resource_path('images/info.png')
self.url_label.setText(strings._('gui_url_label_one_time', True)) self.url_label.setText(strings._('gui_url_label', True).format(info_image))
# Show a Tool Tip explaining the lifecycle of this URL
if self.settings.get('save_private_key'):
if self.settings.get('close_after_first_download'):
self.url_label.setToolTip(strings._('gui_url_label_onetime_and_persistent', True))
else:
self.url_label.setToolTip(strings._('gui_url_label_persistent', True))
else: else:
self.url_label.setText(strings._('gui_url_label', True)) if self.settings.get('close_after_first_download'):
self.url_label.setToolTip(strings._('gui_url_label_onetime', True))
else:
self.url_label.setToolTip(strings._('gui_url_label_stay_open', True))
self.url_label.show() self.url_label.show()
self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug)) self.url.setText('http://{0:s}/{1:s}'.format(self.app.onion_host, self.web.slug))
@ -213,7 +206,7 @@ class ServerStatus(QtWidgets.QWidget):
Toggle starting or stopping the server. Toggle starting or stopping the server.
""" """
if self.status == self.STATUS_STOPPED: if self.status == self.STATUS_STOPPED:
if self.timer_enabled: if self.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 # 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) 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 the timeout has actually passed already before the user hit Start, refuse to start the server.
@ -227,6 +220,7 @@ class ServerStatus(QtWidgets.QWidget):
self.stop_server() self.stop_server()
elif self.status == self.STATUS_WORKING: elif self.status == self.STATUS_WORKING:
self.cancel_server() self.cancel_server()
self.button_clicked.emit()
def start_server(self): def start_server(self):
""" """

View File

@ -86,12 +86,14 @@ class SettingsDialog(QtWidgets.QDialog):
stealth_details.setWordWrap(True) stealth_details.setWordWrap(True)
stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) stealth_details.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
stealth_details.setOpenExternalLinks(True) stealth_details.setOpenExternalLinks(True)
stealth_details.setMinimumSize(stealth_details.sizeHint())
self.stealth_checkbox = QtWidgets.QCheckBox() self.stealth_checkbox = QtWidgets.QCheckBox()
self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked) self.stealth_checkbox.setCheckState(QtCore.Qt.Unchecked)
self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True)) self.stealth_checkbox.setText(strings._("gui_settings_stealth_option", True))
hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True)) hidservauth_details = QtWidgets.QLabel(strings._('gui_settings_stealth_hidservauth_string', True))
hidservauth_details.setWordWrap(True) hidservauth_details.setWordWrap(True)
hidservauth_details.setMinimumSize(hidservauth_details.sizeHint())
hidservauth_details.hide() hidservauth_details.hide()
self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True)) self.hidservauth_copy_button = QtWidgets.QPushButton(strings._('gui_copy_hidservauth', True))

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>Error 404</title> <title>Error 404</title>
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
<style type="text/css"> <style type="text/css">
body { body {
background-color: #FFC4D5; background-color: #FFC4D5;

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>OnionShare</title> <title>OnionShare</title>
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
<style> <style>
body { body {
background-color: #222222; background-color: #222222;

View File

@ -2,104 +2,137 @@
<html> <html>
<head> <head>
<title>OnionShare</title> <title>OnionShare</title>
<link href="data:image/x-icon;base64,{{favicon_b64}}" rel="icon" type="image/x-icon" />
<style type="text/css"> <style type="text/css">
body { .clearfix:after {
background-color: #222222; content: ".";
color: #ffffff; display: block;
text-align: center; clear: both;
font-family: sans-serif; visibility: hidden;
padding: 5em 1em; line-height: 0;
} height: 0;
.button { }
-moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
-webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
box-shadow:inset 0px 1px 0px 0px #cae3fc;
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #4197ee) );
background:-moz-linear-gradient( center top, #79bbff 5%, #4197ee 100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#4197ee');
background-color:#79bbff;
-webkit-border-top-left-radius:12px;
-moz-border-radius-topleft:12px;
border-top-left-radius:12px;
-webkit-border-top-right-radius:12px;
-moz-border-radius-topright:12px;
border-top-right-radius:12px;
-webkit-border-bottom-right-radius:12px;
-moz-border-radius-bottomright:12px;
border-bottom-right-radius:12px;
-webkit-border-bottom-left-radius:12px;
-moz-border-radius-bottomleft:12px;
border-bottom-left-radius:12px;
text-indent:0;
border:1px solid #469df5;
display:inline-block;
color:#ffffff;
font-size:29px;
font-weight:bold;
font-style:normal;
height:50px;
line-height:50px;
text-decoration:none;
text-align:center;
text-shadow:1px 1px 0px #287ace;
padding: 0 20px;
}
.button:hover {
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #4197ee), color-stop(1, #79bbff) );
background:-moz-linear-gradient( center top, #4197ee 5%, #79bbff 100% );
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4197ee', endColorstr='#79bbff');
background-color:#4197ee;
}.button:active {
position:relative;
top:1px;
}
.download-size { body {
color: #999999; margin: 0;
} font-family: Helvetica;
.download-description { }
padding: 10px;
} header {
.file-list { border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 50px auto 0 auto; background: #fcfcfc;
padding: 10px; background: -webkit-linear-gradient(top, #fcfcfc 0%, #f2f2f2 100%);
text-align: left; padding: 0.8rem;
background-color: #333333; }
}
.file-list th { header .logo {
padding: 5px; vertical-align: middle;
font-weight: bold; width: 45px;
color: #999999; height: 45px;
} }
.file-list td {
padding: 5px; header h1 {
} display: inline-block;
margin: 0 0 0 0.5rem;
vertical-align: middle;
font-weight: normal;
font-size: 1.5rem;
color: #666666;
}
header .right {
float: right;
font-size: .75rem;
}
header .right ul li {
display: inline;
margin: 0 0 0 .5rem;
font-size: 1rem;
}
header .button {
color: #ffffff;
background-color: #4e064f;
padding: 10px;
border-radius: 5px;
text-decoration: none;
margin-left: 1rem;
cursor: pointer;
}
table.file-list {
width: 100%;
margin: 0 auto;
border-collapse: collapse;
}
table.file-list th {
text-align: left;
text-transform: uppercase;
font-weight: normal;
color: #666666;
padding: 0.5rem;
}
table.file-list tr {
border-bottom: 1px solid #e0e0e0;
}
table.file-list td {
white-space: nowrap;
padding: 0.5rem 10rem 0.5rem 0.8rem;
}
table.file-list td img {
vertical-align: middle;
margin-right: 0.5rem;
}
table.file-list td:last-child {
width: 100%;
}
</style> </style>
<meta name="onionshare-filename" content="{{ filename }}"> <meta name="onionshare-filename" content="{{ filename }}">
<meta name="onionshare-filesize" content="{{ filesize }}"> <meta name="onionshare-filesize" content="{{ filesize }}">
</head> </head>
<body> <body>
<p><a class="button" href='/{{ slug }}/download'>{{ filename }} &#x25BC;</a></p>
<p class="download-size"><strong title="{{ filesize }} bytes">{{ filesize_human }} (compressed)</strong></p> <header class="clearfix">
<p class="download-description">This zip file contains the following contents:</p> <div class="right">
<ul>
<li>Total size: <strong>{{ filesize_human }}</strong> (compressed)</li>
<li><a class="button" href='/{{ slug }}/download'>Download Files</a></li>
</ul>
</div>
<img class="logo" src="data:image/png;base64,{{logo_b64}}" title="OnionShare">
<h1>OnionShare</h1>
</header>
<table class="file-list"> <table class="file-list">
<tr> <tr>
<th>Type</th> <th>Filename</th>
<th>Name</th>
<th>Size</th> <th>Size</th>
<th></th>
</tr> </tr>
{% for info in file_info.dirs %} {% for info in file_info.dirs %}
<tr> <tr>
<td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQXOpwjbQAAAM5JREFUSMftlksOwyAMRDFCbNjkDL1kz9K75Cq9AZF3kYIM7oqoP/KpcFK1mRUrnsbW2Fbq0N+JiPhZRMS1OZAfzLzocwCAGmC9V2Vhjduarj8CzynGqIwxsDl4SXV267E4uFTN33X8deCxDyV1XXeRYJo1UUDEa9M0pxoRK+b4HiqRcz3nVAJKRK+TSxqaGbrkNKUkBn0oNSK2+T0MA1dau9NzO4QwuvPen1lAxQtEsq/vNpSWhvZ9v/3IRMTWOQezkyuEoKy14tfHoU11A6Mr5AxrpuMVAAAAAElFTkSuQmCC" /></td> <td>
<td>{{ info.basename }}</td> <img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ folder_b64 }}" />
{{ info.basename }}
</td>
<td>{{ info.size_human }}</td> <td>{{ info.size_human }}</td>
<td></td>
</tr> </tr>
{% endfor %} {% endfor %}
{% for info in file_info.files %} {% for info in file_info.files %}
<tr> <tr>
<td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQqYvRvfAAAAJFJREFUSMftlUkOwCAIAEWJ//+uIUivtYmtG7VN8KYHRhlA5zYtuB6klCTG2BcEAHrBeN4ws3jvX3lxQZmFiogMgVesVrhKXlvgakKf4KqVdAdXL+Ea3O8aILh0GnUMEhjtw9mLbUv1tx33fgItynBVoN+k2hybY3Nsjg08Bs45q4GYuQ4OIQARLYcSkUPEoiUP4j5IFasKOnUAAAAASUVORK5CYII=" /></td> <td>
<td>{{ info.basename }}</td> <img width="30" height="30" title="" alt="" src="data:image/png;base64,{{ file_b64 }}" />
{{ info.basename }}
</td>
<td>{{ info.size_human }}</td> <td>{{ info.size_human }}</td>
<td></td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

BIN
share/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
share/images/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

BIN
share/images/web_file.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

BIN
share/images/web_folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View File

@ -13,9 +13,9 @@
"no_available_port": "Could not start the Onion service as there was no available port.", "no_available_port": "Could not start the Onion service as there was no available port.",
"download_page_loaded": "Download page loaded", "download_page_loaded": "Download page loaded",
"other_page_loaded": "Address loaded", "other_page_loaded": "Address loaded",
"close_on_timeout": "Closing automatically because timeout was reached", "close_on_timeout": "Stopped because timer expired",
"closing_automatically": "Closing automatically because download finished", "closing_automatically": "Stopped because download finished",
"timeout_download_still_running": "Waiting for download to complete before auto-stopping", "timeout_download_still_running": "Waiting for download to complete",
"large_filesize": "Warning: Sending large files could take hours", "large_filesize": "Warning: Sending large files could take hours",
"error_tails_invalid_port": "Invalid value, port must be an integer", "error_tails_invalid_port": "Invalid value, port must be an integer",
"error_tails_unknown_root": "Unknown error with Tails root process", "error_tails_unknown_root": "Unknown error with Tails root process",
@ -44,8 +44,10 @@
"gui_copy_hidservauth": "Copy HidServAuth", "gui_copy_hidservauth": "Copy HidServAuth",
"gui_downloads": "Downloads:", "gui_downloads": "Downloads:",
"gui_canceled": "Canceled", "gui_canceled": "Canceled",
"gui_copied_url": "Copied address to clipboard", "gui_copied_url_title": "Copied OnionShare address",
"gui_copied_hidservauth": "Copied HidServAuth line to clipboard", "gui_copied_url": "The OnionShare address has been copied to clipboard",
"gui_copied_hidservauth_title": "Copied HidServAuth",
"gui_copied_hidservauth": "The HidServAuth line has been copied to clipboard",
"gui_starting_server1": "Starting Tor onion service...", "gui_starting_server1": "Starting Tor onion service...",
"gui_starting_server2": "Crunching files...", "gui_starting_server2": "Crunching files...",
"gui_please_wait": "Starting... Click to cancel", "gui_please_wait": "Starting... Click to cancel",
@ -133,11 +135,15 @@
"gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.", "gui_server_timeout_expired": "The chosen timeout has already expired.\nPlease update the timeout and then you may start sharing.",
"share_via_onionshare": "Share via OnionShare", "share_via_onionshare": "Share via OnionShare",
"gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved address)", "gui_save_private_key_checkbox": "Use a persistent address\n(unchecking will delete any saved address)",
"persistent_url_in_use": "This share is using a persistent address",
"gui_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using <b>Tor Browser</b>:", "gui_url_description": "<b>Anyone</b> with this link can <b>download</b> your files using <b>Tor Browser</b>:",
"gui_url_label": "Your Download Address", "gui_url_label": "Your Download Address <img src={} />",
"gui_url_label_one_time": "Your One-Time Download Address", "gui_url_label_persistent": "This share will not stop automatically<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
"gui_url_label_stay_open": "This share will not stop automatically",
"gui_url_label_onetime": "This share will stop after the first download",
"gui_url_label_onetime_and_persistent": "This share will stop after the first download<br><br>Every share will have the same address (to use one-time addresses, disable persistence in the Settings)",
"gui_status_indicator_stopped": "Ready to Share", "gui_status_indicator_stopped": "Ready to Share",
"gui_status_indicator_working": "Starting...", "gui_status_indicator_working": "Starting...",
"gui_status_indicator_started": "Sharing" "gui_status_indicator_started": "Sharing",
"gui_file_info": "{} Files, {}",
"gui_file_info_single": "{} File, {}"
} }

View File

@ -1,13 +1,15 @@
import sys
# Force tests to look for resources in the source code tree
sys.onionshare_dev_mode = True
import os import os
import shutil import shutil
import sys
import tempfile import tempfile
import pytest import pytest
from onionshare import common from onionshare import common
@pytest.fixture @pytest.fixture
def temp_dir_1024(): def temp_dir_1024():
""" Create a temporary directory that has a single file of a """ Create a temporary directory that has a single file of a