diff options
author | Micah Lee <micah@micahflee.com> | 2018-11-26 18:07:12 -0800 |
---|---|---|
committer | Micah Lee <micah@micahflee.com> | 2018-11-26 18:07:12 -0800 |
commit | 942f4f6bac805f332c4c51efeabe01cc539d0fd1 (patch) | |
tree | 82df871f2b7e8e2e3c70ebf431f28e714b537eff | |
parent | 01baf3d6fde55a83368a85ace8632079ceb61c25 (diff) | |
parent | 6bfe9d4476b894e7d0dbb54f4bb689637ead0ad0 (diff) | |
download | onionshare-942f4f6bac805f332c4c51efeabe01cc539d0fd1.tar.gz onionshare-942f4f6bac805f332c4c51efeabe01cc539d0fd1.zip |
Merge branch 'develop' into 406_osx_sandbox
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | onionshare/settings.py | 10 | ||||
-rw-r--r-- | onionshare_gui/mode/history.py | 9 | ||||
-rw-r--r-- | onionshare_gui/receive_mode/uploads.py | 330 | ||||
-rw-r--r-- | onionshare_gui/share_mode/downloads.py | 163 | ||||
-rw-r--r-- | screenshots/client.png | bin | 40602 -> 0 bytes | |||
-rw-r--r-- | screenshots/onionshare-receive-client.png | bin | 0 -> 59463 bytes | |||
-rw-r--r-- | screenshots/onionshare-receive-server.png | bin | 0 -> 68038 bytes | |||
-rw-r--r-- | screenshots/onionshare-share-client.png | bin | 0 -> 42423 bytes | |||
-rw-r--r-- | screenshots/onionshare-share-server.png | bin | 0 -> 55027 bytes |
10 files changed, 32 insertions, 510 deletions
@@ -1,22 +1,38 @@ # OnionShare -[![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) - [OnionShare](https://onionshare.org) lets you securely and anonymously share files of any size. It works by starting a web server, making it accessible as a Tor Onion Service, and generating an unguessable URL to access and download the files. It does _not_ require setting up a separate server or using a third party file-sharing service. You host the files on your own computer and use a Tor Onion Service to make it temporarily accessible over the internet. The receiving user just needs to open the URL in Tor Browser to download the file. ## Documentation To learn how OnionShare works, what its security properties are, and how to use it, check out the [wiki](https://github.com/micahflee/onionshare/wiki). -## Downloading Onionshare +## Downloading OnionShare + +You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). + +For Ubuntu-like distributions, you could use this PPA to get the latest version: + +``` +sudo add-apt-repository ppa:micahflee/ppa +sudo apt install -y onionshare +``` + +OnionShare also may be available in your operating system's package manager: -You can download OnionShare for Windows and macOS from the [OnionShare website](https://onionshare.org). It should be available in your package manager for Linux, and it's included by default in [Tails](https://tails.boum.org). +[![Packaging status](https://repology.org/badge/vertical-allrepos/onionshare.svg)](https://repology.org/metapackage/onionshare/versions) ## Developing OnionShare You can set up your development environment to build OnionShare yourself by following [these instructions](/BUILD.md). You may also subscribe to our developers mailing list [here](https://lists.riseup.net/www/info/onionshare-dev). -# Screenshots +Test status: [![CircleCI](https://circleci.com/gh/micahflee/onionshare.svg?style=svg)](https://circleci.com/gh/micahflee/onionshare) + +# Screenshots + +![Share mode OnionShare](/screenshots/onionshare-share-server.png) + +![Share mode Tor Browser](/screenshots/onionshare-share-client.png) + +![Receive mode OnionShare](/screenshots/onionshare-receive-server.png) -![Server Screenshot](/screenshots/appdata-server.png) -![Client Screenshot](/screenshots/client.png) +![Receive mode Tor Browser](/screenshots/onionshare-receive-client.png) diff --git a/onionshare/settings.py b/onionshare/settings.py index ca3fccfa..3397c1fd 100644 --- a/onionshare/settings.py +++ b/onionshare/settings.py @@ -127,14 +127,18 @@ class Settings(object): """ Returns the path of the default Downloads directory for receive mode. """ - # TODO: Test in Windows, though it looks like it should work - # https://docs.python.org/3/library/os.path.html#os.path.expanduser + if self.common.platform == "Darwin": - # We can't use os.path.expanduser in macOS because in the sandbox it + # We can't use os.path.expanduser() in macOS because in the sandbox it # returns the path to the sandboxed homedir real_homedir = pwd.getpwuid(os.getuid()).pw_dir return os.path.join(real_homedir, 'OnionShare') + elif self.common.platform == "Windows": + # On Windows, os.path.expanduser() needs to use backslash, or else it + # retains the forward slash, which breaks opening the folder in explorer. + return os.path.expanduser('~\OnionShare') else: + # All other OSes return os.path.expanduser('~/OnionShare') def load(self): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 38e0fed3..e72a3838 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -184,16 +184,11 @@ class UploadHistoryItemFile(QtWidgets.QWidget): # macOS elif self.common.platform == 'Darwin': - # TODO: Implement opening folder with file selected in macOS - # This seems helpful: https://stackoverflow.com/questions/3520493/python-show-in-finder - self.common.log('UploadHistoryItemFile', 'open_folder', 'not implemented for Darwin yet') + subprocess.call(['open', '-R', abs_filename]) # Windows elif self.common.platform == 'Windows': - # TODO: Implement opening folder with file selected in Windows - # This seems helpful: https://stackoverflow.com/questions/6631299/python-opening-a-folder-in-explorer-nautilus-mac-thingie - self.common.log('UploadHistoryItemFile', 'open_folder', 'not implemented for Windows yet') - + subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) class UploadHistoryItem(HistoryItem): def __init__(self, common, id, content_length): diff --git a/onionshare_gui/receive_mode/uploads.py b/onionshare_gui/receive_mode/uploads.py deleted file mode 100644 index 68c94b1c..00000000 --- a/onionshare_gui/receive_mode/uploads.py +++ /dev/null @@ -1,330 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -import os -import subprocess -import textwrap -from datetime import datetime -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings -from ..widgets import Alert - - -class File(QtWidgets.QWidget): - def __init__(self, common, filename): - super(File, self).__init__() - self.common = common - - self.common.log('File', '__init__', 'filename: {}'.format(filename)) - - self.filename = filename - self.dir = None - self.started = datetime.now() - - # Filename label - self.filename_label = QtWidgets.QLabel(self.filename) - self.filename_label_width = self.filename_label.width() - - # File size label - self.filesize_label = QtWidgets.QLabel() - self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) - self.filesize_label.hide() - - # Folder button - folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) - folder_icon = QtGui.QIcon(folder_pixmap) - self.folder_button = QtWidgets.QPushButton() - self.folder_button.clicked.connect(self.open_folder) - self.folder_button.setIcon(folder_icon) - self.folder_button.setIconSize(folder_pixmap.rect().size()) - self.folder_button.setFlat(True) - self.folder_button.hide() - - # Layouts - layout = QtWidgets.QHBoxLayout() - layout.addWidget(self.filename_label) - layout.addWidget(self.filesize_label) - layout.addStretch() - layout.addWidget(self.folder_button) - self.setLayout(layout) - - def update(self, uploaded_bytes, complete): - self.filesize_label.setText(self.common.human_readable_filesize(uploaded_bytes)) - self.filesize_label.show() - - if complete: - self.folder_button.show() - - def set_dir(self, dir): - self.dir = dir - - def rename(self, new_filename): - self.filename = new_filename - self.filename_label.setText(self.filename) - - def open_folder(self): - """ - Open the downloads folder, with the file selected, in a cross-platform manner - """ - self.common.log('File', 'open_folder') - - if not self.dir: - return - - abs_filename = os.path.join(self.dir, self.filename) - - # Linux - if self.common.platform == 'Linux' or self.common.platform == 'BSD': - try: - # If nautilus is available, open it - subprocess.Popen(['nautilus', abs_filename]) - except: - Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) - - # macOS - elif self.common.platform == 'Darwin': - # TODO: Implement opening folder with file selected in macOS - # This seems helpful: https://stackoverflow.com/questions/3520493/python-show-in-finder - self.common.log('File', 'open_folder', 'not implemented for Darwin yet') - - # Windows - elif self.common.platform == 'Windows': - # TODO: Implement opening folder with file selected in Windows - # This seems helpful: https://stackoverflow.com/questions/6631299/python-opening-a-folder-in-explorer-nautilus-mac-thingie - self.common.log('File', 'open_folder', 'not implemented for Windows yet') - - -class Upload(QtWidgets.QWidget): - def __init__(self, common, upload_id, content_length): - super(Upload, self).__init__() - self.common = common - self.upload_id = upload_id - self.content_length = content_length - self.started = datetime.now() - - # Label - self.label = QtWidgets.QLabel(strings._('gui_upload_in_progress').format(self.started.strftime("%b %d, %I:%M%p"))) - - # Progress bar - self.progress_bar = QtWidgets.QProgressBar() - self.progress_bar.setTextVisible(True) - self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) - self.progress_bar.setMinimum(0) - self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) - - # This layout contains file widgets - self.files_layout = QtWidgets.QVBoxLayout() - self.files_layout.setContentsMargins(0, 0, 0, 0) - files_widget = QtWidgets.QWidget() - files_widget.setStyleSheet(self.common.css['receive_file']) - files_widget.setLayout(self.files_layout) - - # Layout - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.label) - layout.addWidget(self.progress_bar) - layout.addWidget(files_widget) - layout.addStretch() - self.setLayout(layout) - - # We're also making a dictionary of file widgets, to make them easier to access - self.files = {} - - def update(self, progress): - """ - Using the progress from Web, update the progress bar and file size labels - for each file - """ - total_uploaded_bytes = 0 - for filename in progress: - total_uploaded_bytes += progress[filename]['uploaded_bytes'] - - # Update the progress bar - self.progress_bar.setMaximum(self.content_length) - self.progress_bar.setValue(total_uploaded_bytes) - - elapsed = datetime.now() - self.started - if elapsed.seconds < 10: - pb_fmt = strings._('gui_download_upload_progress_starting').format( - self.common.human_readable_filesize(total_uploaded_bytes)) - else: - estimated_time_remaining = self.common.estimated_time_remaining( - total_uploaded_bytes, - self.content_length, - self.started.timestamp()) - pb_fmt = strings._('gui_download_upload_progress_eta').format( - self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) - - # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" - for filename in list(progress): - # Add a new file if needed - if filename not in self.files: - self.files[filename] = File(self.common, filename) - self.files_layout.addWidget(self.files[filename]) - - # Update the file - self.files[filename].update(progress[filename]['uploaded_bytes'], progress[filename]['complete']) - - def rename(self, old_filename, new_filename): - self.files[old_filename].rename(new_filename) - self.files[new_filename] = self.files.pop(old_filename) - - def set_dir(self, filename, dir): - self.files[filename].set_dir(dir) - - def finished(self): - # Hide the progress bar - self.progress_bar.hide() - - # Change the label - self.ended = self.started = datetime.now() - if self.started.year == self.ended.year and self.started.month == self.ended.month and self.started.day == self.ended.day: - if self.started.hour == self.ended.hour and self.started.minute == self.ended.minute: - text = strings._('gui_upload_finished').format( - self.started.strftime("%b %d, %I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range').format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%I:%M%p") - ) - else: - text = strings._('gui_upload_finished_range').format( - self.started.strftime("%b %d, %I:%M%p"), - self.ended.strftime("%b %d, %I:%M%p") - ) - self.label.setText(text) - - -class Uploads(QtWidgets.QScrollArea): - """ - The uploads chunk of the GUI. This lists all of the active upload - progress bars, as well as information about each upload. - """ - def __init__(self, common): - super(Uploads, self).__init__() - self.common = common - self.common.log('Uploads', '__init__') - - self.resizeEvent = None - - self.uploads = {} - - self.setWindowTitle(strings._('gui_uploads')) - self.setWidgetResizable(True) - self.setMinimumHeight(150) - self.setMinimumWidth(350) - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) - self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) - self.vbar = self.verticalScrollBar() - self.vbar.rangeChanged.connect(self.resizeScroll) - - uploads_label = QtWidgets.QLabel(strings._('gui_uploads')) - uploads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_uploads_label = QtWidgets.QLabel(strings._('gui_no_uploads')) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history')) - self.clear_history_button.clicked.connect(self.reset) - self.clear_history_button.hide() - - - self.uploads_layout = QtWidgets.QVBoxLayout() - - widget = QtWidgets.QWidget() - layout = QtWidgets.QVBoxLayout() - layout.addWidget(uploads_label) - layout.addWidget(self.no_uploads_label) - layout.addWidget(self.clear_history_button) - layout.addLayout(self.uploads_layout) - layout.addStretch() - widget.setLayout(layout) - self.setWidget(widget) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.vbar.setValue(maximum) - - def add(self, upload_id, content_length): - """ - Add a new upload. - """ - self.common.log('Uploads', 'add', 'upload_id: {}, content_length: {}'.format(upload_id, content_length)) - # Hide the no_uploads_label - self.no_uploads_label.hide() - # Show the clear_history_button - self.clear_history_button.show() - - # Add it to the list - upload = Upload(self.common, upload_id, content_length) - self.uploads[upload_id] = upload - self.uploads_layout.addWidget(upload) - - def update(self, upload_id, progress): - """ - Update the progress of an upload. - """ - self.uploads[upload_id].update(progress) - - def rename(self, upload_id, old_filename, new_filename): - """ - Rename a file, which happens if the filename already exists in downloads_dir. - """ - self.uploads[upload_id].rename(old_filename, new_filename) - - def finished(self, upload_id): - """ - An upload has finished. - """ - self.uploads[upload_id].finished() - - def cancel(self, upload_id): - """ - Update an upload progress bar to show that it has been canceled. - """ - self.common.log('Uploads', 'cancel', 'upload_id: {}'.format(upload_id)) - self.uploads[upload_id].cancel() - - def reset(self): - """ - Reset the uploads back to zero - """ - self.common.log('Uploads', 'reset') - for upload in self.uploads.values(): - upload.close() - self.uploads_layout.removeWidget(upload) - self.uploads = {} - - self.no_uploads_label.show() - self.clear_history_button.hide() - self.resize(self.sizeHint()) - - def resizeEvent(self, event): - width = self.frameGeometry().width() - try: - for upload in self.uploads.values(): - for item in upload.files.values(): - item.filename_label.setText(textwrap.fill(item.filename, 30)) - item.adjustSize() - except: - pass diff --git a/onionshare_gui/share_mode/downloads.py b/onionshare_gui/share_mode/downloads.py deleted file mode 100644 index fe1e91b8..00000000 --- a/onionshare_gui/share_mode/downloads.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -""" -OnionShare | https://onionshare.org/ - -Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -import time -from PyQt5 import QtCore, QtWidgets, QtGui - -from onionshare import strings - - -class Download(object): - def __init__(self, common, download_id, total_bytes): - self.common = common - - self.download_id = download_id - self.started = time.time() - self.total_bytes = total_bytes - self.downloaded_bytes = 0 - - # Progress bar - self.progress_bar = QtWidgets.QProgressBar() - self.progress_bar.setTextVisible(True) - self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) - self.progress_bar.setMinimum(0) - self.progress_bar.setMaximum(total_bytes) - self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) - self.progress_bar.total_bytes = total_bytes - - # Start at 0 - self.update(0) - - def update(self, downloaded_bytes): - self.downloaded_bytes = downloaded_bytes - - self.progress_bar.setValue(downloaded_bytes) - if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_download_upload_progress_complete').format( - self.common.format_seconds(time.time() - self.started)) - else: - elapsed = time.time() - self.started - if elapsed < 10: - # Wait a couple of seconds for the download rate to stabilize. - # This prevents a "Windows copy dialog"-esque experience at - # the beginning of the download. - pb_fmt = strings._('gui_download_upload_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) - else: - pb_fmt = strings._('gui_download_upload_progress_eta').format( - self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) - - self.progress_bar.setFormat(pb_fmt) - - def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) - - @property - def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) - - -class Downloads(QtWidgets.QScrollArea): - """ - The downloads chunk of the GUI. This lists all of the active download - progress bars. - """ - def __init__(self, common): - super(Downloads, self).__init__() - self.common = common - - self.downloads = {} - - self.setWindowTitle(strings._('gui_downloads')) - self.setWidgetResizable(True) - self.setMinimumHeight(150) - self.setMinimumWidth(350) - self.setWindowIcon(QtGui.QIcon(common.get_resource_path('images/logo.png'))) - self.setWindowFlags(QtCore.Qt.Sheet | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.CustomizeWindowHint) - self.vbar = self.verticalScrollBar() - self.vbar.rangeChanged.connect(self.resizeScroll) - - downloads_label = QtWidgets.QLabel(strings._('gui_downloads')) - downloads_label.setStyleSheet(self.common.css['downloads_uploads_label']) - self.no_downloads_label = QtWidgets.QLabel(strings._('gui_no_downloads')) - self.clear_history_button = QtWidgets.QPushButton(strings._('gui_clear_history')) - self.clear_history_button.clicked.connect(self.reset) - self.clear_history_button.hide() - - self.downloads_layout = QtWidgets.QVBoxLayout() - - widget = QtWidgets.QWidget() - layout = QtWidgets.QVBoxLayout() - layout.addWidget(downloads_label) - layout.addWidget(self.no_downloads_label) - layout.addWidget(self.clear_history_button) - layout.addLayout(self.downloads_layout) - layout.addStretch() - widget.setLayout(layout) - self.setWidget(widget) - - def resizeScroll(self, minimum, maximum): - """ - Scroll to the bottom of the window when the range changes. - """ - self.vbar.setValue(maximum) - - def add(self, download_id, total_bytes): - """ - Add a new download progress bar. - """ - # Hide the no_downloads_label - self.no_downloads_label.hide() - # Show the clear_history_button - self.clear_history_button.show() - - # Add it to the list - download = Download(self.common, download_id, total_bytes) - self.downloads[download_id] = download - self.downloads_layout.addWidget(download.progress_bar) - - def update(self, download_id, downloaded_bytes): - """ - Update the progress of a download progress bar. - """ - self.downloads[download_id].update(downloaded_bytes) - - def cancel(self, download_id): - """ - Update a download progress bar to show that it has been canceled. - """ - self.downloads[download_id].cancel() - - def reset(self): - """ - Reset the downloads back to zero - """ - for download in self.downloads.values(): - self.downloads_layout.removeWidget(download.progress_bar) - download.progress_bar.close() - self.downloads = {} - - self.no_downloads_label.show() - self.clear_history_button.hide() - self.resize(self.sizeHint()) diff --git a/screenshots/client.png b/screenshots/client.png Binary files differdeleted file mode 100644 index fdf69fd0..00000000 --- a/screenshots/client.png +++ /dev/null diff --git a/screenshots/onionshare-receive-client.png b/screenshots/onionshare-receive-client.png Binary files differnew file mode 100644 index 00000000..7702a4bd --- /dev/null +++ b/screenshots/onionshare-receive-client.png diff --git a/screenshots/onionshare-receive-server.png b/screenshots/onionshare-receive-server.png Binary files differnew file mode 100644 index 00000000..0962a74f --- /dev/null +++ b/screenshots/onionshare-receive-server.png diff --git a/screenshots/onionshare-share-client.png b/screenshots/onionshare-share-client.png Binary files differnew file mode 100644 index 00000000..7cb6c3c8 --- /dev/null +++ b/screenshots/onionshare-share-client.png diff --git a/screenshots/onionshare-share-server.png b/screenshots/onionshare-share-server.png Binary files differnew file mode 100644 index 00000000..76830876 --- /dev/null +++ b/screenshots/onionshare-share-server.png |