diff --git a/.travis.yml b/.travis.yml
index 6d324010..9010e77a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,12 @@ python:
- "nightly"
# command to install dependencies
install:
- - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls
+ - pip install Flask==0.12 stem==1.5.4 pytest-cov coveralls flake8
+before_script:
+ # stop the build if there are Python syntax errors or undefined names
+ - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+ - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# command to run tests
script: pytest --cov=onionshare test/
after_success:
diff --git a/install/onionshare.desktop b/install/onionshare.desktop
index 256a7b50..fbac3660 100644
--- a/install/onionshare.desktop
+++ b/install/onionshare.desktop
@@ -1,9 +1,11 @@
[Desktop Entry]
Name=OnionShare
Comment=Share a file securely and anonymously over Tor
+Comment[da]=Del en fil sikkert og anonymt over Tor
Exec=/usr/bin/onionshare-gui
Terminal=false
Type=Application
Icon=/usr/share/pixmaps/onionshare80.xpm
Categories=Network;
Keywords=tor;anonymity;privacy;onion service;file sharing;file hosting;
+Keywords[da]=tor;anonymitet;privatliv;onion-tjeneste;fildeling;filhosting;
diff --git a/install/onionshare.nsi b/install/onionshare.nsi
index 0a9e6cf2..5b53b048 100644
--- a/install/onionshare.nsi
+++ b/install/onionshare.nsi
@@ -201,6 +201,10 @@ Section "install"
File "${BINPATH}\share\html\index.html"
SetOutPath "$INSTDIR\share\images"
+ File "${BINPATH}\share\images\download_completed.png"
+ File "${BINPATH}\share\images\download_completed_none.png"
+ File "${BINPATH}\share\images\download_in_progress.png"
+ File "${BINPATH}\share\images\download_in_progress_none.png"
File "${BINPATH}\share\images\favicon.ico"
File "${BINPATH}\share\images\file_delete.png"
File "${BINPATH}\share\images\info.png"
@@ -383,6 +387,10 @@ FunctionEnd
Delete "$INSTDIR\share\html\404.html"
Delete "$INSTDIR\share\html\denied.html"
Delete "$INSTDIR\share\html\index.html"
+ Delete "$INSTDIR\share\images\download_completed.png"
+ Delete "$INSTDIR\share\images\download_completed_none.png"
+ Delete "$INSTDIR\share\images\download_in_progress.png"
+ Delete "$INSTDIR\share\images\download_in_progress_none.png"
Delete "$INSTDIR\share\images\favicon.ico"
Delete "$INSTDIR\share\images\file_delete.png"
Delete "$INSTDIR\share\images\info.png"
diff --git a/install/scripts/onionshare-nautilus.py b/install/scripts/onionshare-nautilus.py
index 6674dd18..ed50fb23 100644
--- a/install/scripts/onionshare-nautilus.py
+++ b/install/scripts/onionshare-nautilus.py
@@ -64,7 +64,7 @@ class OnionShareExtension(GObject.GObject, Nautilus.MenuProvider):
"""
def url2path(self,url):
- file_uri = url.get_activation_uri()
+ file_uri = url.get_activation_uri()
arg_uri = file_uri[7:]
path = urllib.url2pathname(arg_uri)
return path
diff --git a/onionshare/common.py b/onionshare/common.py
index 25b901ee..79d62ca9 100644
--- a/onionshare/common.py
+++ b/onionshare/common.py
@@ -23,6 +23,7 @@ import inspect
import os
import platform
import random
+import re
import socket
import sys
import tempfile
@@ -56,8 +57,10 @@ def get_platform():
"""
Returns the platform OnionShare is running on.
"""
- return platform.system()
-
+ plat = platform.system()
+ if re.match('^.*BSD$', plat):
+ plat = 'BSD'
+ return plat
def get_resource_path(filename):
"""
@@ -73,7 +76,7 @@ def get_resource_path(filename):
# While running tests during stdeb bdist_deb, look 3 directories up for the share folder
prefix = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(prefix)))), 'share')
- elif p == 'Linux':
+ elif p == 'BSD' or p == 'Linux':
# Assume OnionShare is installed systemwide in Linux, since we're not running in dev mode
prefix = os.path.join(sys.prefix, 'share/onionshare')
@@ -107,7 +110,7 @@ def get_tor_paths():
tor_geo_ip_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip')
tor_geo_ipv6_file_path = os.path.join(base_path, 'Resources', 'Tor', 'geoip6')
obfs4proxy_file_path = os.path.join(base_path, 'Resources', 'Tor', 'obfs4proxy')
- elif p == 'OpenBSD' or p == 'FreeBSD':
+ elif p == 'BSD':
tor_path = '/usr/local/bin/tor'
tor_geo_ip_file_path = '/usr/local/share/tor/geoip'
tor_geo_ipv6_file_path = '/usr/local/share/tor/geoip6'
diff --git a/onionshare/onion.py b/onionshare/onion.py
index 668de051..41e44781 100644
--- a/onionshare/onion.py
+++ b/onionshare/onion.py
@@ -131,7 +131,7 @@ class Onion(object):
self.stealth = False
self.service_id = None
- self.system = platform.system()
+ self.system = common.get_platform()
# Is bundled tor supported?
if (self.system == 'Windows' or self.system == 'Darwin') and getattr(sys, 'onionshare_dev_mode', False):
@@ -183,7 +183,7 @@ class Onion(object):
raise OSError(strings._('no_available_port'))
self.tor_torrc = os.path.join(self.tor_data_directory.name, 'torrc')
else:
- # Linux and Mac can use unix sockets
+ # Linux, Mac and BSD can use unix sockets
with open(common.get_resource_path('torrc_template')) as f:
torrc_template = f.read()
self.tor_control_port = None
@@ -318,7 +318,7 @@ class Onion(object):
# guessing the socket file name next
if not found_tor:
try:
- if self.system == 'Linux':
+ if self.system == 'Linux' or self.system == 'BSD':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
elif self.system == 'Darwin':
socket_file_path = '/run/user/{}/Tor/control.socket'.format(os.geteuid())
diff --git a/onionshare/web.py b/onionshare/web.py
index 0fedb29b..d16ca251 100644
--- a/onionshare/web.py
+++ b/onionshare/web.py
@@ -315,12 +315,14 @@ def download(slug_candidate):
percent = (1.0 * downloaded_bytes / zip_filesize) * 100
# only output to stdout if running onionshare in CLI mode, or if using Linux (#203, #304)
- if not gui_mode or common.get_platform() == 'Linux':
+ plat = common.get_platform()
+ if not gui_mode or plat == 'Linux' or plat == 'BSD':
sys.stdout.write(
"\r{0:s}, {1:.2f}% ".format(common.human_readable_filesize(downloaded_bytes), percent))
sys.stdout.flush()
add_request(REQUEST_PROGRESS, path, {'id': download_id, 'bytes': downloaded_bytes})
+ done = False
except:
# looks like the download was canceled
done = True
diff --git a/onionshare_gui/__init__.py b/onionshare_gui/__init__.py
index 14c76617..24e627bb 100644
--- a/onionshare_gui/__init__.py
+++ b/onionshare_gui/__init__.py
@@ -35,8 +35,8 @@ class Application(QtWidgets.QApplication):
and the quick keyboard shortcut.
"""
def __init__(self):
- system = platform.system()
- if system == 'Linux':
+ system = common.get_platform()
+ if system == 'Linux' or system == 'BSD':
self.setAttribute(QtCore.Qt.AA_X11InitThreads, True)
QtWidgets.QApplication.__init__(self, sys.argv)
self.installEventFilter(self)
diff --git a/onionshare_gui/file_selection.py b/onionshare_gui/file_selection.py
index 6a9d2e21..f08073db 100644
--- a/onionshare_gui/file_selection.py
+++ b/onionshare_gui/file_selection.py
@@ -268,10 +268,6 @@ class FileSelection(QtWidgets.QVBoxLayout):
super(FileSelection, self).__init__()
self.server_on = False
- # 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.currentItemChanged.connect(self.update)
@@ -289,7 +285,6 @@ class FileSelection(QtWidgets.QVBoxLayout):
button_layout.addWidget(self.delete_button)
# Add the widgets
- self.addWidget(self.info_label)
self.addWidget(self.file_list)
self.addLayout(button_layout)
@@ -299,23 +294,6 @@ class FileSelection(QtWidgets.QVBoxLayout):
"""
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
if self.server_on:
self.add_button.hide()
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py
index af90ad58..50f920eb 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -109,6 +109,30 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.vbar = self.downloads_container.verticalScrollBar()
self.downloads_container.hide() # downloads start out hidden
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_in_progress_download_count = QtWidgets.QLabel()
+ self.info_in_progress_download_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
+ self.info_in_progress_download_count.hide()
+
+ self.info_completed_downloads_count = QtWidgets.QLabel()
+ self.info_completed_downloads_count.setStyleSheet('QLabel { font-size: 12px; color: #666666; }')
+ self.info_completed_downloads_count.hide()
+
+ self.info_layout.addWidget(self.info_label)
+ self.info_layout.addStretch()
+ self.info_layout.addWidget(self.info_in_progress_download_count)
+ self.info_layout.addWidget(self.info_completed_downloads_count)
+
+ self.info_widget = QtWidgets.QWidget()
+ self.info_widget.setLayout(self.info_layout)
+ self.info_widget.hide()
# Settings button on the status bar
self.settings_button = QtWidgets.QPushButton()
@@ -169,6 +193,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Main layout
self.layout = QtWidgets.QVBoxLayout()
+ self.layout.addWidget(self.info_widget)
self.layout.addLayout(self.file_selection)
self.layout.addWidget(self.primary_action)
central_widget = QtWidgets.QWidget()
@@ -200,10 +225,26 @@ class OnionShareGui(QtWidgets.QMainWindow):
def update_primary_action(self):
# Show or hide primary action layout
- if self.file_selection.file_list.count() > 0:
+ file_count = self.file_selection.file_list.count()
+ if file_count > 0:
self.primary_action.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 = 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_widget.show()
+
else:
self.primary_action.hide()
+ self.info_widget.hide()
# Resize window
self.adjustSize()
@@ -324,6 +365,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
# Hide and reset the downloads if we have previously shared
self.downloads_container.hide()
self.downloads.reset_downloads()
+ self.reset_info_counters()
+ self.info_in_progress_download_count.show()
+ self.info_completed_downloads_count.show()
self.status_bar.clearMessage()
self.server_share_status_label.setText('')
@@ -459,6 +503,9 @@ class OnionShareGui(QtWidgets.QMainWindow):
# 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)
@@ -528,6 +575,8 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.downloads_container.show() # show the downloads layout
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.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_started_title', True), strings._('systray_download_started_message', True))
@@ -542,6 +591,13 @@ class OnionShareGui(QtWidgets.QMainWindow):
if event["data"]["bytes"] == web.zip_filesize:
if self.systemTray.supportsMessages() and self.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 web.get_stay_open():
self.server_status.stop_server()
@@ -550,9 +606,15 @@ class OnionShareGui(QtWidgets.QMainWindow):
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"] == 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.settings.get('systray_notifications'):
self.systemTray.showMessage(strings._('systray_download_canceled_title', True), strings._('systray_download_canceled_message', True))
@@ -608,6 +670,37 @@ class OnionShareGui(QtWidgets.QMainWindow):
# 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_in_progress_download_count.show()
+ self.info_completed_downloads_count.show()
+
+ def update_downloads_completed(self, count):
+ """
+ Update the 'Downloads completed' info widget.
+ """
+ if count == 0:
+ self.info_completed_downloads_image = common.get_resource_path('images/download_completed_none.png')
+ else:
+ self.info_completed_downloads_image = common.get_resource_path('images/download_completed.png')
+ self.info_completed_downloads_count.setText('
{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_download_image = common.get_resource_path('images/download_in_progress_none.png')
+ else:
+ self.info_in_progress_download_image = common.get_resource_path('images/download_in_progress.png')
+ self.info_in_progress_download_count.setText('
{1:d}'.format(self.info_in_progress_download_image, count))
+ self.info_in_progress_download_count.setToolTip(strings._('info_in_progress_downloads_tooltip', True).format(count))
+
def closeEvent(self, e):
common.log('OnionShareGui', 'closeEvent')
try:
diff --git a/setup.py b/setup.py
index dcbe42fb..23e1ea17 100644
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,17 @@ author_email = 'micah@micahflee.com'
url = 'https://github.com/micahflee/onionshare'
license = 'GPL v3'
keywords = 'onion, share, onionshare, tor, anonymous, web server'
+data_files=[
+ (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']),
+ (os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']),
+ (os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']),
+ (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
+ (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
+ (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
+ (os.path.join(sys.prefix, 'share/onionshare/html'), file_list('share/html')),
+ ]
+if platform.system() != 'OpenBSD':
+ data_files.append(('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py']))
setup(
name='onionshare', version=version,
@@ -54,14 +65,5 @@ setup(
packages=['onionshare', 'onionshare_gui'],
include_package_data=True,
scripts=['install/scripts/onionshare', 'install/scripts/onionshare-gui'],
- data_files=[
- (os.path.join(sys.prefix, 'share/applications'), ['install/onionshare.desktop']),
- (os.path.join(sys.prefix, 'share/appdata'), ['install/onionshare.appdata.xml']),
- (os.path.join(sys.prefix, 'share/pixmaps'), ['install/onionshare80.xpm']),
- (os.path.join(sys.prefix, 'share/onionshare'), file_list('share')),
- (os.path.join(sys.prefix, 'share/onionshare/images'), file_list('share/images')),
- (os.path.join(sys.prefix, 'share/onionshare/locale'), file_list('share/locale')),
- (os.path.join(sys.prefix, 'share/onionshare/html'), file_list('share/html')),
- ('/usr/share/nautilus-python/extensions/', ['install/scripts/onionshare-nautilus.py'])
- ]
+ data_files=data_files
)
diff --git a/share/images/download_completed.png b/share/images/download_completed.png
new file mode 100644
index 00000000..e68fe5a2
Binary files /dev/null and b/share/images/download_completed.png differ
diff --git a/share/images/download_completed_none.png b/share/images/download_completed_none.png
new file mode 100644
index 00000000..8dbd6939
Binary files /dev/null and b/share/images/download_completed_none.png differ
diff --git a/share/images/download_in_progress.png b/share/images/download_in_progress.png
new file mode 100644
index 00000000..19694659
Binary files /dev/null and b/share/images/download_in_progress.png differ
diff --git a/share/images/download_in_progress_none.png b/share/images/download_in_progress_none.png
new file mode 100644
index 00000000..2d61dba4
Binary files /dev/null and b/share/images/download_in_progress_none.png differ
diff --git a/share/locale/en.json b/share/locale/en.json
index d703fd0c..6a4b7430 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -145,5 +145,7 @@
"gui_status_indicator_working": "Starting...",
"gui_status_indicator_started": "Sharing",
"gui_file_info": "{} Files, {}",
- "gui_file_info_single": "{} File, {}"
+ "gui_file_info_single": "{} File, {}",
+ "info_in_progress_downloads_tooltip": "{} download(s) in progress",
+ "info_completed_downloads_tooltip": "{} download(s) completed"
}